Process virtual memory - linux

Can process have a few virtual pages pointing to the same physical address in his same address space ?
I would like that virt_page1---> physical-X
and also that virt_page2 ---> physical-X
How can it be done ? Should it be done from the kernel space ?
what routines involves ?
if I would like to map shared library like this:
7ff2a90d8000-7ff2a928d000 r-xp 00000000 08:02 4980747 /lib/x86_64-linux-gnu/libc-2.15.so
7ff2a928d000-7ff2a948d000 ---p 001b5000 08:02 4980747 /lib/x86_64-linux-gnu/libc-2.15.so
7ff2a948d000-7ff2a9491000 r--p 001b5000 08:02 4980747 /lib/x86_64-linux-gnu/libc-2.15.so
7ff2a9491000-7ff2a9493000 rw-p 001b9000 08:02 4980747 /lib/x86_64-linux-gnu/libc-2.15.so
I see the mapping are private, does it mean that I can't map them again to other virtual address ? should I change the linker for that ?
Update:
While disabling ASLR I did the following:
int main(void)
{
int fd = open("/lib/x86_64-linux-gnu/libc-2.15.so", O_RDONLY);
void* f1 = mmap(0, 1748*1024, PROT_READ|PROT_EXEC, MAP_PRIVATE, fd, 0);
void *f2 = (void*)0x00007ffff7a1a000;
if (memcmp(f1, f2, 1748*1024) != 0) {
printf("DIFFER\n");
}
while(1);
return 0;
}
This is the .so mapping when there is no ASLR
00007ffff7a1a000 1748K r-x-- /lib/x86_64-linux-gnu/libc-2.15.so
So I mmap the regions of the above to other page & I got this:
00007ffff7e26000 1748K r-x-- /lib/x86_64-linux-gnu/libc-2.15.so
While I compare f1 & f2 I see the same data,
is it to say that I have now to virtual regions mapped to the same physical address which is the shared library portion of 1748K ?

Yes it's possible from the user space. The simplest method is to mmap the same file twice.
char templ[] = "XXXXXXXX";
int fd = mkstemp(templ);
ftruncate(fd, 1024);
void* f1 = mmap(0, 1024, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
void* f2 = mmap(0, 1024, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
strcpy (f1, "foo bar baz weep quup");
printf ("%p %s\n", f1, (char*)f1);
printf ("%p %s\n", f2, (char*)f2);

Related

Where are "str" values allocated? Is it in the heap?

I am new to Rust and I try to figure out where things are allocated.
I use Rust 1.64.0 on Ubuntu 22.04.0 (jammy):
$ rustc --version
rustc 1.64.0 (a55dd71d5 2022-09-19)
$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description: Ubuntu 22.04.1 LTS
Release: 22.04
Codename: jammy
I try to explore the executable produce by compiling this very basic Rust program:
fn tester() {
let borrowed_string: &str = "world";
println!("{}", borrowed_string);
}
fn main() { tester(); }
Compilation (OK, this is overkill... but I want to be sure that I have all I need for using GDB):
$ cargo build --config "profile.dev.debug=true" --config "profile.dev.opt-level=0" --profile=dev
Compiling variables v0.1.0 (/home/denis/Documents/github/rust-playground/variables)
Finished dev [unoptimized + debuginfo] target(s) in 0.71s
Run rust-gdb, set the brakpoint and run the program:
$ rust-gdb target/debug/variables
GNU gdb (Ubuntu 12.0.90-0ubuntu1) 12.0.90
...
Reading symbols from target/debug/variables...
(gdb) b 3
Breakpoint 1 at 0x7959: file src/main.rs, line 3.
(gdb) r
Starting program: /home/denis/Documents/github/rust-playground/variables/target/debug/variables
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Breakpoint 1, variables::tester () at src/main.rs:3
3 println!("{}", borrowed_string);
(gdb)
s
Print the memory mapping;
(gdb) info proc map
process 6985
Mapped address spaces:
Start Addr End Addr Size Offset Perms objfile
0x555555554000 0x55555555a000 0x6000 0x0 r--p /home/denis/Documents/github/rust-playground/variables/target/debug/variables
0x55555555a000 0x555555591000 0x37000 0x6000 r-xp /home/denis/Documents/github/rust-playground/variables/target/debug/variables
0x555555591000 0x55555559f000 0xe000 0x3d000 r--p /home/denis/Documents/github/rust-playground/variables/target/debug/variables
0x5555555a0000 0x5555555a3000 0x3000 0x4b000 r--p /home/denis/Documents/github/rust-playground/variables/target/debug/variables
0x5555555a3000 0x5555555a4000 0x1000 0x4e000 rw-p /home/denis/Documents/github/rust-playground/variables/target/debug/variables
0x5555555a4000 0x5555555c5000 0x21000 0x0 rw-p [heap]
0x7ffff7d5c000 0x7ffff7d5f000 0x3000 0x0 rw-p
0x7ffff7d5f000 0x7ffff7d87000 0x28000 0x0 r--p /usr/lib/x86_64-linux-gnu/libc.so.6
0x7ffff7d87000 0x7ffff7f1c000 0x195000 0x28000 r-xp /usr/lib/x86_64-linux-gnu/libc.so.6
0x7ffff7f1c000 0x7ffff7f74000 0x58000 0x1bd000 r--p /usr/lib/x86_64-linux-gnu/libc.so.6
0x7ffff7f74000 0x7ffff7f78000 0x4000 0x214000 r--p /usr/lib/x86_64-linux-gnu/libc.so.6
0x7ffff7f78000 0x7ffff7f7a000 0x2000 0x218000 rw-p /usr/lib/x86_64-linux-gnu/libc.so.6
0x7ffff7f7a000 0x7ffff7f87000 0xd000 0x0 rw-p
0x7ffff7f87000 0x7ffff7f8a000 0x3000 0x0 r--p /usr/lib/x86_64-linux-gnu/libgcc_s.so.1
0x7ffff7f8a000 0x7ffff7fa1000 0x17000 0x3000 r-xp /usr/lib/x86_64-linux-gnu/libgcc_s.so.1
0x7ffff7fa1000 0x7ffff7fa5000 0x4000 0x1a000 r--p /usr/lib/x86_64-linux-gnu/libgcc_s.so.1
0x7ffff7fa5000 0x7ffff7fa6000 0x1000 0x1d000 r--p /usr/lib/x86_64-linux-gnu/libgcc_s.so.1
0x7ffff7fa6000 0x7ffff7fa7000 0x1000 0x1e000 rw-p /usr/lib/x86_64-linux-gnu/libgcc_s.so.1
0x7ffff7fb8000 0x7ffff7fb9000 0x1000 0x0 ---p
0x7ffff7fb9000 0x7ffff7fbd000 0x4000 0x0 rw-p
0x7ffff7fbd000 0x7ffff7fc1000 0x4000 0x0 r--p [vvar]
0x7ffff7fc1000 0x7ffff7fc3000 0x2000 0x0 r-xp [vdso]
0x7ffff7fc3000 0x7ffff7fc5000 0x2000 0x0 r--p /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
0x7ffff7fc5000 0x7ffff7fef000 0x2a000 0x2000 r-xp /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
0x7ffff7fef000 0x7ffff7ffa000 0xb000 0x2c000 r--p /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
0x7ffff7ffb000 0x7ffff7ffd000 0x2000 0x37000 r--p /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
0x7ffff7ffd000 0x7ffff7fff000 0x2000 0x39000 rw-p /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
0x7ffffffde000 0x7ffffffff000 0x21000 0x0 rw-p [stack]
0xffffffffff600000 0xffffffffff601000 0x1000 0x0 --xp [vsyscall]
(gdb)
We have:
HEAP: [0x5555555A4000, 0x5555555C4FFF]
STACK: [0x7FFFFFFDE000, 0x7FFFFFFFEFFF]
I assume that the data "world" is allocated in the heap. Thus, let's find out... but look at what I obtain!!!
(gdb) find 0x5555555A4000, 0x5555555C4FFF, "world"
0x5555555a4ba0
1 pattern found.
(gdb) find 0x5555555A4000, 0x5555555C4FFF, "world"
0x5555555a4be0
1 pattern found.
(gdb) find 0x5555555A4000, 0x5555555C4FFF, "world"
0x5555555a4c20
1 pattern found.
(gdb) find 0x5555555A4000, 0x5555555C4FFF, "world"
0x5555555a4c60
1 pattern found.
(gdb) find 0x5555555A4000, 0x5555555C4FFF, "world"
0x5555555a4ca0
1 pattern found.
(gdb)
Please note that:
0x5555555A4BA0 -> 0x5555555A4BE0 => 64
0x5555555A4BE0 -> 0x5555555A4C20 => 64
0x5555555A4C20 -> 0x5555555A4C60 => 64
Question: why does the position of the data change from one scan to the next?
(This question may not be related specifically to Rust.)
(gdb) p borrowed_string
$1 = "world"
(gdb) ptype borrowed_string
type = struct &str {
data_ptr: *mut u8,
length: usize,
}
(gdb) p borrowed_string.data_ptr
$2 = (*mut u8) 0x555555591000
(gdb) x/5c 0x555555591000
0x555555591000: 119 'w' 111 'o' 114 'r' 108 'l' 100 'd'
We have:
0x5555555A4000: start of the heap (included)
0x5555555C5000: end of the heap (excluded), and start of the "uncharted" memory territory
0x555555591000: address of the data "world"
0x7FFFF7D5F000: end of the "uncharted" memory territory
Question: what is this "uncharted" memory territory between 0x5555555C5000 (included) and 0x7FFFF7D5F000 (excluded)?
EDIT:
As mentioned in the comments below, I made a mistake. The data "world" is "before" the heap... The comments make sense. Thank you.
Question: what is this "uncharted" memory territory between 0x5555555C5000 (included) and 0x7FFFF7D5F000 (excluded)?
It's nothing. It's memory which is not mapped.
Where are "str" values allocated? Is it in the heap?
Your memory map answers the question:
0x555555591000 0x55555559f000 0xe000 0x3d000 r--p /home/denis/Documents/github/rust-playground/variables/target/debug/variables
This is one of the areas where the binary itself is mapped. Most likely the "rodata" segment of the executable. String literals are generally "allocated" in the binary itself, then a static reference is loaded.
More generally str values are not allocated, they're a reference to data living somewhere else which could be on the heap (if the reference was obtained from a String or Box<str> or some pointers), in the binary (literals, static includes), or even on the stack (e.g. you can create an array on the stack then pass it through std::str::from_utf8 and get an &str out of it)
We have: HEAP: [0x5555555A4000, 0x5555555C4FFF]
That's... not really helpful or useful or true, frankly: modern systems have largely moved away from linear heaps and to memory map heaps. While Linux software still makes some limited use of (s)brk, BSDs qualify them as "historical curiosities". If you assume the heap is contained within these bounds you're going to face lots of surprises.
Question: why does the position of the data change from one scan to the next?
Because the debugger is allocating stuff in context of the program, I assume? So you might be finding the data gdb itself is storing.

why mremap after only access 4096 byte memory?

The below code begin mmap(initlen=10), later remap(nsize=400000) and access remap memory address
access 0x7ffff7f8c000 to 0x7ffff7f8cfff is ok, but in the 0x7ffff7f8d000 cause access error
#define _GNU_SOURCE
#include <stdio.h>
#include <limits.h>
#include <stdint.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <fcntl.h>
int main()
{
int i;
char *p;
void *base;
const int initlen = 10;
const int nsize = 400000;
const char *fname = "/tmp/task.0";
int fd = open(fname, O_CREAT|O_RDWR, 0600);
if (fd == -1) {
return 1;
}
if (ftruncate(fd, initlen) < 0) {
return 1;
}
base = mmap(NULL, initlen, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
if (base == MAP_FAILED) {
return 1;
}
// now remap big page.
base = mremap(base, initlen, nsize, MREMAP_MAYMOVE);
if (base == MAP_FAILED) {
printf("mremap fail, %s\n", strerror(errno));
return 1;
}
p = base;
for (i = 0; i < nsize; i++) {
printf("%p\n", p);
*p = i % CHAR_MAX;
++p;
}
return 0;
}
mremap after gdb show the "/tmp/task.0" region is 0x7ffff7f8c000-0x7ffff7fee000, access error in the 0x7ffff7f8d000, why???
37 base = mremap(base, initlen, nsize, MREMAP_MAYMOVE);
(gdb)
38 if (base == MAP_FAILED) {
(gdb)
43 p = base;
(gdb)
44 for (i = 0; i < nsize; i++) {
(gdb) p base
$1 = (void *) 0x7ffff7f8c000
(gdb) i proc mappings
process 3333
Mapped address spaces:
Start Addr End Addr Size Offset objfile
0x400000 0x401000 0x1000 0x0 /tmp/a.out
0x600000 0x601000 0x1000 0x0 /tmp/a.out
0x601000 0x602000 0x1000 0x1000 /tmp/a.out
0x7ffff7a0e000 0x7ffff7bd1000 0x1c3000 0x0 /usr/lib64/libc-2.17.so
0x7ffff7bd1000 0x7ffff7dd0000 0x1ff000 0x1c3000 /usr/lib64/libc-2.17.so
0x7ffff7dd0000 0x7ffff7dd4000 0x4000 0x1c2000 /usr/lib64/libc-2.17.so
0x7ffff7dd4000 0x7ffff7dd6000 0x2000 0x1c6000 /usr/lib64/libc-2.17.so
0x7ffff7dd6000 0x7ffff7ddb000 0x5000 0x0
0x7ffff7ddb000 0x7ffff7dfd000 0x22000 0x0 /usr/lib64/ld-2.17.so
0x7ffff7f8c000 0x7ffff7fee000 0x62000 0x0 /tmp/task.0
0x7ffff7fee000 0x7ffff7ff1000 0x3000 0x0
0x7ffff7ff9000 0x7ffff7ffa000 0x1000 0x0
0x7ffff7ffa000 0x7ffff7ffc000 0x2000 0x0 [vdso]
0x7ffff7ffc000 0x7ffff7ffd000 0x1000 0x21000 /usr/lib64/ld-2.17.so
0x7ffff7ffd000 0x7ffff7ffe000 0x1000 0x22000 /usr/lib64/ld-2.17.so
0x7ffff7ffe000 0x7ffff7fff000 0x1000 0x0
0x7ffffffde000 0x7ffffffff000 0x21000 0x0 [stack]
0xffffffffff600000 0xffffffffff601000 0x1000 0x0 [vsyscall]
Program received signal SIGBUS, Bus error.
0x0000000000400831 in main () at ./test.c:46
46 *p = i % CHAR_MAX;
(gdb) p p
$2 = 0x7ffff7f8d000 <Address 0x7ffff7f8d000 out of bounds>
It is not related to mremap. It is just how file mappings work.
SIGBUS means that you tried to access file region past its EOF (see manual page mmap(3)).
SIGBUS Attempted access to a portion of the buffer that does not correspond to the file (for example, beyond the end of the file, including the case where another process has truncated the file).
This is different from SIGSEGV, which is sent when you try to access virtual address which doesn't exist in your process, or when protection error occurs (e.g. attempt to write in read-only address).
To answer the question why you can access first 0xfff bytes without getting SIGBUS (even though file size is only 10 bytes), this is because memory management happens in terms of 4096 byte pages. Note however that even though you can access bytes 10..4095, this region is not backed by file. Everything you write into those bytes, won't be written into file.

Reading /proc/pid/mem from ptraced process returns EOF

Context
I've been working in a program for my final assignment and I've found the following strange behaviour.
I've coded a tracer in order to be able to read/write memory from child processes. My intention is to read the currently executed instruction at a given point, then disassemble it in order to get some information about memory operands and such.
For testing purposes a simple HelloWorld written in C is used.
Information
The code of the tracer I've written, albeit simplified in order to be more understandable, is this:
size_t tracer::readMem(ADDR_t offset, char *buff, size_t len) {
REQUIRE(_state != TRCS_UNINITIALISED);
if (_memsdescr < 0 || fcntl(_memsdescr, F_GETFL) < 0) {
_memsdescr = open(("/proc/" + to_string(_child_pid_t) + "/mem").c_str(), O_LARGEFILE);
if (_memsdescr < 0) {
logmanager::getInstance ().emplaceBasicLogger ("tracer")
.log ( SLVL_ERROR, "Process\' memory could not be "
" opened. \n");
PANIC;
} else {
logmanager::getInstance ().emplaceBasicLogger ("tracer")
.logfmt ( SLVL_DEBUG, "Opened process' memory. %lx bytes long\n",
lseek(_memsdescr, 0, SEEK_END));
}
}
ASSERT(offset <= lseek(_memsdescr, 0, SEEK_END));
int ret = pread(_memsdescr, buff, len, offset);
if (ret < 0) {
logmanager::getInstance ().emplaceBasicLogger ("tracer")
.logfmt( SLVL_ERROR, "Error reading from memory descriptor: %s\n", sys_errlist[errno]);
}
return ret;
}
The code that controls the exectution is the following. Basically all it does is to read 15 bytes chunks from /proc/mem. The address of those chunks is obtained by getting the value of the RIP (instruction pointer) after a call to ptrace(PTRACE_SINGLESTEP). This means all the memory I try to read should be mapped in the process' memory space.
trc.load (filename);
trc.launchProgram();
cout << " Started with pid " << trc.getChildPid() << endl << endl;
//memspacy::memory_map::printSystemProcVmap(trc.getChildPid());
//inj.prop_setTraceSyscalls (true);
while (trc.prop_getState () != memspacy::TRCS_STOPPED) {
//if (trc.isSyscall()){
// trc.showSyscall();
//}
//HERE IS WHERE THE DISASSEMBLY takes place
if (trc.readMem(trc.peekReg(a_RIP), inst_buff, MAX_ARCH_INST_LEN)
&& dec.disassemble()) {
dec.printFormatted();
}
trc.singleStep();
}
Issue
The HelloWorld should be comprised of several thousand instructions, but the output I get is this.
mov %rsp, %rdi
add %al, (%rax)
push %rdi
push %rsi
push %rsp
mov %edi, %ebx
in %dx, %al
xor %ecx, -0x3f(%rax)
invalid
Useful facts
It seems that after a couple instructions the read function stops getting any data al all. No error is thrown, the only problem is that reading the memory returns 0 bytes. This would mean that the EOF is reached as per the information in the read() manpage, but lseek() returns a size of 0xFFFFFFFFFFFF, so there should be no problem on that end. Aso, all the reads fall within the mapped regions since I'm using the program counter as the offset.
I can not really think of any thing other than page permissions, but they all have read permissions set, otherwise it wouldn't even execute. The process is properly Ptraced, and the execution runs just fine, with the expected behaviour, even the registers are the exactly the same as in the control test (a test used to check the original behaviour).
My current guess is that at some point it reaches the end of a mapped region and that renders the descriptor useless, hence that "invalid" instruction at the end, but even opening the file on each read the result does not change.
Data
Here is the memory map and the read offset of the last valid read.
00400000-00401000 r-xp 00000000 08:06 7602542 /home/amontes/workspace/memspacy_build/assets/test/test
00600000-00602000 rw-p 00000000 08:06 7602542 /home/amontes/workspace/memspacy_build/assets/test/test
**7fe3eb602000-7fe3eb625000 r-xp 00000000 08:11 657171 /lib/x86_64-linux-gnu/ld-2.19.so**
7fe3eb824000-7fe3eb826000 rw-p 00022000 08:11 657171 /lib/x86_64-linux-gnu/ld-2.19.so
7fe3eb826000-7fe3eb827000 rw-p 00000000 00:00 0
7fff57783000-7fff577a4000 rw-p 00000000 00:00 0 [stack]
7fff577fe000-7fff57800000 r-xp 00000000 00:00 0 [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]
Last valid offset 7fe3eb606a7c -> This shows the invalid instruction
First invalid offset 7fe3eb606a7d -> This returns EOF
Any help or any idea would be really appreciated. Thank you.
Well, I don't know if it was a bug caused by some update, or a very specific kernel version or whatever you want to call it. After a clean install of the OS, everything works correctly. I can get the instruction stream, and the the read function always returns data.
Before wiping the HD and prior to the installation, I tried ptrace(PTRACE_PEEKDATA) without luck. Now everything works as it should.
I don't really believe this question is going to help anybody, but sometimes a clean start is the way to go. As much as I hate to admit it, it's happened to me from time to time, not always related to coding software.

Why I can see the several same segments in the /proc/pid/maps output?

Test is on the 32 bit Linux
The code is as below:
int foo(int a, int b)
{
int c = a + b;
return c;
}
int main()
{
int e = 0;
int d = foo(1, 2);
printf("%d\n", d);
scanf("%d", &e);
return 0;
}
and when I use cat /proc/pid/maps to see the memory layout, it seems that I can see three
text segment for my code and the library.
ubuntu% cat /proc/2191/maps
08048000-08049000 r-xp 00000000 08:01 1467306 /home/shuai/work/asm/test1
08049000-0804a000 r--p 00000000 08:01 1467306 /home/shuai/work/asm/test1
0804a000-0804b000 rw-p 00001000 08:01 1467306 /home/shuai/work/asm/test1
09137000-09158000 rw-p 00000000 00:00 0 [heap]
b75c6000-b75c7000 rw-p 00000000 00:00 0
b75c7000-b776b000 r-xp 00000000 08:01 3149924 /lib/i386-linux-gnu/libc-2.15.so
b776b000-b776d000 r--p 001a4000 08:01 3149924 /lib/i386-linux-gnu/libc-2.15.so
b776d000-b776e000 rw-p 001a6000 08:01 3149924 /lib/i386-linux-gnu/libc-2.15.so
b776e000-b7771000 rw-p 00000000 00:00 0
b7780000-b7784000 rw-p 00000000 00:00 0
b7784000-b7785000 r-xp 00000000 00:00 0 [vdso]
b7785000-b77a5000 r-xp 00000000 08:01 3149914 /lib/i386-linux-gnu/ld-2.15.so
b77a5000-b77a6000 r--p 0001f000 08:01 3149914 /lib/i386-linux-gnu/ld-2.15.so
b77a6000-b77a7000 rw-p 00020000 08:01 3149914 /lib/i386-linux-gnu/ld-2.15.so
bfd47000-bfd68000 rw-p 00000000 00:00 0 [stack]
Could any one give me some guide about this issue? Thank you a lot!
Please mind the values in columns 3 (starting offset) and 2 (permissions). Really you have the same part mapped twice, in lines 1 and 2 for your binary file, but, in line 3, it's different. It's permitted to map the same file separately multiple times; different systems could skip merging this into one VM map entry, so it could reflect mapping history but not the current state jist.
If you see at library mappings you could easily find the law that any library is mapped separately:
With permission to read and execute: the main code which shouldn't be changed.
With permission to read: constant data area without code allowed.
With permission to read and write: it combines non-constant data area and relocation tables of shared objects.
Having the same starting 4K binary file area mapped twice could be explained with RTLD logic which differs from an arbitrary library logic due to bootstrapping needs. I don't treat it so important, more so it could easily differ on platform specifics.
Note that the three sections for each file have different permissions: read-only, read-write, and read-execute. This is for security: the code section (read-execute) can't be written to through exploits, and the segment that can be written can't be executed.

Determine a thread's stack size and location

how can I determine the base of a thread's stack and its size under Linux? Is there an available C/C++ API or a way to find out in gdb?
thanks
Here's a different method of doing this, involving reading /proc/self/maps. Unlike some of the other methods, it doesn't require special instrumentation at the start of your program and gives you an exact position for the end of the stack.
If you try cat /proc/self/maps, you get something like this:
00400000-0040c000 r-xp 00000000 08:01 6039736 /usr/bin/cat
0060b000-0060c000 r--p 0000b000 08:01 6039736 /usr/bin/cat
0060c000-0060d000 rw-p 0000c000 08:01 6039736 /usr/bin/cat
00908000-00929000 rw-p 00000000 00:00 0 [heap]
7fcdb1c68000-7fcdb1e01000 r-xp 00000000 08:01 6032628 /usr/lib/libc-2.21.so
7fcdb1e01000-7fcdb2000000 ---p 00199000 08:01 6032628 /usr/lib/libc-2.21.so
7fcdb2000000-7fcdb2004000 r--p 00198000 08:01 6032628 /usr/lib/libc-2.21.so
7fcdb2004000-7fcdb2006000 rw-p 0019c000 08:01 6032628 /usr/lib/libc-2.21.so
7fcdb2006000-7fcdb200a000 rw-p 00000000 00:00 0
7fcdb200a000-7fcdb202c000 r-xp 00000000 08:01 6032717 /usr/lib/ld-2.21.so
7fcdb21f5000-7fcdb21f8000 rw-p 00000000 00:00 0
7fcdb2209000-7fcdb222b000 rw-p 00000000 00:00 0
7fcdb222b000-7fcdb222c000 r--p 00021000 08:01 6032717 /usr/lib/ld-2.21.so
7fcdb222c000-7fcdb222d000 rw-p 00022000 08:01 6032717 /usr/lib/ld-2.21.so
7fcdb222d000-7fcdb222e000 rw-p 00000000 00:00 0
7ffe78c41000-7ffe78c62000 rw-p 00000000 00:00 0 [stack]
7ffe78dba000-7ffe78dbc000 r--p 00000000 00:00 0 [vvar]
7ffe78dbc000-7ffe78dbe000 r-xp 00000000 00:00 0 [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]
As you can see, there is a [stack] entry. This is probably what you're looking for.
An example program to parse this line out:
#include <stdio.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char *argv[]) {
FILE *file = fopen("/proc/self/maps", "r");
char line[1024];
void *result = NULL;
while (!feof(file)) {
if (fgets(line, sizeof(line) / sizeof(char), file) == NULL) {
break;
}
unsigned long start, end, offset;
unsigned int devma, devmi, ino;
char perms[6];
char path[128];
if (sscanf(line, "%lx-%lx %5s %lx %d:%d %d %127s", &start, &end, &perms, &offset, &devma, &devmi, &ino, &path) != 8) {
continue; // could not parse. fail gracefully and try again on the next line.
}
if (strcmp(path, "[stack]") == 0) { // use [stack:TID] for a thread besides the main thread
printf("Stack found from %lx to %lx\n", start, end);
break;
}
}
fclose(file);
return 0;
}
This will print something like:
Stack found from 7fff91834000 to 7fff91855000
This is probably fairly close to what you're looking for.
If you're okay with not getting the exact top of the stack (which might be enough for some applications), traversing the frame pointers could work:
// main.c
#include <stdint.h>
// -fno-omit-frame-pointer might be required - otherwise you might get a crash
// might not be exactly at the top; but probably very close
void *stack_top() {
void **top;
asm("movq %%rbp, %0" : "=r" (top)); // set top to %rbp - replace with %ebp for 32-bit x86
// if top is higher in memory than the variable, then still part of the stack.
while ((uintptr_t) *top > (uintptr_t) &top) {
top = *top;
}
return top;
}
This works because the %rbp register (or %ebp register on 32-bit) is used to store the pointer to the base of the parent stack frame (where the saved %rbp or %ebp value is) - so we can iteratively traverse this linked list until we reach an invalid address.
Note that stack_top might fail in some cases relating to the &top comparison - my system terminated the linked list with a pointer to a location in the program loading code, so this was the best way that I found to detect this - but you'll want to test this thoroughly. (If anyone has a better way to detect the end of the chain, please add a comment.)
Example test program:
#include <stdio.h>
#include <pthread.h>
void *test_another_layer(int x) {
return stack_top();
}
void *subthread(void *ptr) {
void *st1 = stack_top();
void *st2 = test_another_layer(0);
void *st3 = &ptr;
printf("stack tops 2: %x %x %x\n", st1, st2, st3);
return NULL;
}
int main(int argc, char *argv[]) {
void *st1 = stack_top();
void *st2 = test_another_layer(0);
void *st3 = &argc;
printf("stack tops: %x %x %x\n", st1, st2, st3);
pthread_t ot;
if (pthread_create(&ot, NULL, subthread, NULL) != 0) {
perror("cannot create");
return 1;
}
if (pthread_join(ot, NULL) != 0) {
perror("cannot join");
return 2;
}
return 0;
}

Resources