6

I'm adding a feature to my Linux debugger (I'm using Ptrace to manipulate the traced process as well as libbfd/libopcodes) to unwind the stack and determine if discrepancies exist between each CALL's allocated stack space and a statically derived local variable size, printing the address and local stack size of each frame along the way.

My general methodology is to take the address in the base pointer (EBP/RBP), increment the pointer to should should contain the stored frame pointer, dereference that address, examine it with PTRACE_PEEKDATA and repeat until I dereference an address occupying an area outside the stack.

I know how to check code/data segment registers, but ideally I'd like a method to check if I'm still inside the callstack even if the segmentation has been changed by W^X memory pages or an otherwise nonexecutable stack.

In short, how can I check (in the general case) when I've moved outside the stack without triggering a general protection fault?

(As as aside, I realize I'm operating on the assumption that checking an address's page segment is the ideal methodology here -- perhaps another simpler method exists to determine if an address is within the current process's stack space)

  • 1
    I guess you are working under Linux. Specifying the OS is clearly needed here as it does set the stack base address (with an ASLR offset or not) before starting the process. – perror Apr 5 '13 at 15:19
  • Thank you, I've edited the answer to specify above -- It is indeed linux. – ŹV - Apr 5 '13 at 15:22
  • Highly recommended reading on unwinding: part 1, part 2 (OS X oriented, but many things still apply). – Igor Skochinsky Jun 4 '13 at 18:32
  • Given that you know the stack usage in intel/amd environments you know that the ebp value's would necessarily increase in value for each successive rollback (meaning, accessing the next parent). Also, given you're looking at a Linux environment you can (somewhat safely) assume the 8MB (per thread) stack size limitation. Furthermore, given that (most likely) 4KB pages are in use, you can safely read ebp values that are at a greater address than the current one UNTIL it reaches a different (and obviously higher) page entry. At this point, you can use mmap() to see if the page is there or not... – Nick Aug 22 '16 at 18:43
  • Also, you can detect some forms of 'corrupted' EBP/RBP values by examining the lower 2 bits (32-bit) or lower 3 bits (64-bit) because the ESP/RSP and EBP/RSP values are 4-byte or 8-byte aligned, respectively. – Nick Aug 23 '16 at 16:10
9

So, it is totally untested but here is the result of a few Internet browsing.

First the stack base address is present in /proc/<pid>/maps, then it must be accessible from user-space at some point.

I looked at the code of the pstack command which is printing the content of the stack of a running process. This code is getting the base address from a link_map structure and store it inside the field l_addr. This field is set inside the function readLinkMap():

static void readLinkMap(int pid, ElfN_Addr base, struct link_map *lm, 
                        char *name, unsigned int namelen)
{
  /* base address */
  lm->l_addr = (ElfN_Addr) ptrace(PTRACE_PEEKDATA, pid,
                                  base + offsetof(struct link_map,l_addr), 0);
  /* next element of link map chain */
  if (-1 != (long) lm->l_addr || !errno)
    lm->l_next = (struct link_map *) ptrace(PTRACE_PEEKDATA, pid,
                                            base + offsetof(struct link_map, l_next), 0);
  if ((-1 == (long) lm->l_addr || -1 == (long) lm->l_next) && errno) {
    perror("ptrace");
    quit("can't read target.");
  }

  loadString(pid, base + offsetof(struct link_map, l_name), name, namelen);
}

I guess this is the right way to go. So, I would advise you to take a look at the code of the pstack command (the file is not very long) and to get inspiration from it because it does something extremely similar to what you want (at least if I understand what you said correctly).

Hope this short note will help you a bit.

  • Within the context of the users question, this seems adequate. If one wants to implement backtrace functionality, however, you'll probably need to use dwarf cfi if you want it to be reliable. – broadway Jun 5 '13 at 16:11
  • @broadway: But, dwarf is not always present on all executable files. You can get some help looking at it, but, in my humble opinion, it would be a mistake to totally rely on it and nothing else. – perror Jun 5 '13 at 17:46
  • yeah, you just have to accept the breakage in that case. The frames are often just not going to be there. You can maybe try to heuristically infer the registers in some common cases, though. – broadway Jun 5 '13 at 18:03

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.