Examining the Linux VDSO
anomit | April 18, 2010I have been recently looking into the sysenter/sysexit way of implementing system calls on Linux. It’s then that I came to know about the concept of VDSO (Virtual Dynamic Shared Object). It may look hacky to some but IMO, it’s quite an elegant and practical solution to overcome the incompatibilities that might be introduced if it was left to the userland libraries like libc to use the software interrupt or sysenter/sysexit mechanism. You will get more information about the VDSO here than I could ever dig into.
Even though the post linked above is very informative, it suffers from the same problems that plague most of the resources on linux kernel on the web. A couple of things are outdated there which could seriously put you off if you like to get your hands dirty along with reading such stuff.
-
It states that the VDSO is
a shared object exposed by the kernel at a fixed address in every process’ memory
Unfortunately, this isn’t the case anymore. It might have been true for the <2.6.15 kernels but it certainly isn't that way on my 2.6.32 kernel. To get an idea, try this command a few times:
cat /proc/self/maps | fgrep vdso
This will give you the memory map of the process running `cat` itself. You will see the memory address to which the vdso is mapped is different each time rather than the fixed mapping to 0xffffe000 as the post claims, which brings us to our second problem. -
Assuming the fixed mapping at 0xffffe000, the post tells you to use dd to extract the relevant information by accessing the process' pages through /proc/self/mem.
dd if=/proc/self/mem of=linux-gate.dso bs=4096 skip=1048574 count=1
But things aren't the same now. You will never know the pages to skip over because the VDSO is always mapped at a different location everytime you run the `dd` command. If you don't believe me, try it out yourself.
To overcome this problem, I created this small script in python which will extracts the VDSO from its own mapping into a file and then you can use `objdump` to examine it.
""" This script writes the VDSO to the file linux-gate.dso.1 . Use `objdump -d linux-gate.dso.1` to examine it. You might also want to play around more with the other objdump options and the readelf toolLICENSE: MIT License ( http://www.opensource.org/licenses/mit-license.php ) """ from __future__ import with_statement import os import re ## regex pattern for finding out the memory address range from the output line pattern = re.compile(r'[\w\d]+-[\w\d]+') with open('/proc/self/maps', 'r') as file: for line in file: line = line.rstrip() if '[vdso]' in line: addr_range = pattern.findall(line)[0] start_addr, end_addr = [int(addr, 16) for addr in addr_range.split('-')] fd = os.open('/proc/self/mem', os.O_RDONLY) os.lseek(fd, start_addr, os.SEEK_SET) buf = os.read(fd, (end_addr-start_addr)) with open('linux-gate.dso.1', 'w') as file: file.write(buf) file.close() os.close(fd)
I also created a github gist in case you need to track any further corrections to problems that might arise later on or maybe fork it.







[...] This post was mentioned on Twitter by Anomit Ghosh,
Tweets that mention Truth, Computing and Fail » Examining the Linux VDSO -- Topsy.com | April 18, 2010[...] This post was mentioned on Twitter by Anomit Ghosh, PlanetManipal. PlanetManipal said: Examining the Linux VDSO: I have been recently looking into the sysenter/sysexit way of implementing system calls … http://bit.ly/dBrTcP [...]
Great post. I've been looking for something like that for
Yago Mouriño Mendaña | May 18, 2010Great post. I’ve been looking for something like that for weeks. Thank you.
[...] this blog post from Johan Petersson for another treatment
Stupid tricks at the userspace/kernelspace boundary, part 1 « syskblogd | July 19, 2010[...] this blog post from Johan Petersson for another treatment of the first 3/5 of this, and this blog post from Anomit Ghosh for a Python version of [...]
hmm, why not dd if=/proc/self/mem of=vdso skip=$((0x`cat /proc/self/maps |
Suresh Kumar | July 21, 2010hmm, why not
dd if=/proc/self/mem of=vdso skip=$((0x`cat /proc/self/maps | grep vdso | cut -d'-' -f1`/0x1000)) count=1 bs=$((0x1000))Updated Seem to have missed objdump dd if=/proc/self/mem of=- skip=$((0x`cat /proc/self/maps |
Suresh Kumar | July 21, 2010Updated
Seem to have missed objdump
dd if=/proc/self/mem of=- skip=$((0x`cat /proc/self/maps | grep vdso | cut -d'-' -f1`/0x1000)) count=1 bs=$((0x1000)) | objdump -d --start-address=0xffffe000 -
Thanks for posting this article. I read the original
Ezra Gilbert | September 1, 2010Thanks for posting this article. I read the original posting and was stuck on these very same issues. But when I try to run your script I get error:
[root@asm-99 ~]# ./vdso.py
File “./vdso.py”, line 20
with open(‘/proc/self/maps’, ‘r’) as file:
^
SyntaxError: invalid syntax
Do I need a particular version of python ? I am using 2.4.3.
Thanks
Suresh, Your dd 1-liner gives me the following error: dd if=/proc/self/mem of=vdso
Ezra Gilbert | September 1, 2010Suresh,
Your dd 1-liner gives me the following error:
dd if=/proc/self/mem of=vdso skip=$((0x`cat /proc/self/maps | grep vdso | cut -d’-’ -f1`/0×1000)) count=1 bs=$((0×1000))
dd: reading `/proc/self/mem’: Input/output error
0+0 records in
0+0 records out
0 bytes (0 B) copied, 0.000162585 seconds, 0.0 kB/s
Do I need to do something to make /proc/self/mem readable? I am on 2.6.18 kernel.
Thanks.
Ezra, you need Python version 2.5 and above to support
anomit | September 1, 2010Ezra, you need Python version 2.5 and above to support the context manager concept used by the `with’ statement. More here: http://docs.python.org/release/2.5.2/lib/typecontextmanager.html
Sorry for that. You can create just a simple file object using the normal open() and work with it
.
Here is a version of the python script that works
Ezra Gilbert | September 1, 2010Here is a version of the python script that works with python 2.4.3 (maybe 2.4.x). I basically removed the un-supported “with” statements and replaced os.SEEK_SET with 0 (per http://docs.python.org/library/os.html)
Also, I think the reason Suresh’s 1-line script does not work is because the address read for vdso is for process ‘cat’ and it is not a valid address when process ‘dd’ tries to read from /proc/self/mem. For it to work, the same process that reads the vdso address from /proc/self/maps needs to read linux-gate.so from /proc/self/mem.
-Ezra
The code in the last comment did not come out
Ezra Gilbert | September 1, 2010The code in the last comment did not come out very well. Here is a link to a fork of anomit’s gist above that works with python 2.4.3: http://gist.github.com/560719
Suresh's on-liner doesn't work, because there are different 'selfs' there.
Igor | April 14, 2011Suresh’s on-liner doesn’t work, because there are different ‘selfs’ there.
‘cat /proc/self/maps | …’ produces mapping for ‘cat’, not necessarily identical to that of ‘dd’.
For readability /proc//mem see very good explanation here: http://unix.stackexchange.com/questions/6301/how-do-i-read-from-proc-pid-mem-under-linux
Igor | April 14, 2011For readability /proc//mem see very good explanation here: http://unix.stackexchange.com/questions/6301/how-do-i-read-from-proc-pid-mem-under-linux
You probably have Address Space Layout Randomization (ASLR) turned on.
Brandon Potter | July 21, 2011You probably have Address Space Layout Randomization (ASLR) turned on. It randomizes the virtual addresses within the kernel for security reasons.
To check to see if it is enabled, try:
sudo cat /proc/sys/kernel/randomize_va_space
If it returns 2, the default, then ASLR is turned on.
To turn it off, try:
sudo su
sudo echo 0 > /proc/sys/kernel/randomize_va_space
This overcomes the need to write a clever script to extract the virtual address; the address will remain constant on subsequent runs.
I am not sure what you’re doing with the “dd” command because you’re opening up a snapshot of the “dd” process internals. Previously, you were opening up the “cat” internals. The two should be completely different.
You might try to write a small C program that contains an infinite loop; you can look at the process ID of that program and then look in /proc/PID/maps for the VDSO page offset. I think that “dd” uses the decimal offset for the number of pages, the reason why you used “4096″.
The “dd” command becomes:
dd if=/proc/PID_C_PROGRAM/mem of=linux-gate.dso bs=4096 skip=CALCULATED_OFFSET_IN_PAGES count=1
objdump -d linux-gate.dso
You might try something like the above. I don’t really use objdump for anything other than looking at ELF executables. I don’t really know what /proc/PID/mem contains so I have no idea what the output would look like. It might work though.
I looked at using the "dd" command for a bit.
Brandon Potter | July 22, 2011I looked at using the “dd” command for a bit. I couldn’t figure out how to get it to work with the /proc/pid/mem file. I don’t know what “dd” is using internally to read from the file, but I ended up getting output similar to Ezra; dropping to root does nothing to resolve the issue.
I know that for /proc/pid/pagemap that you can extract the page frame numbers using lseek and read which is similar to the python script above; I could be that “mem” uses something similar. It probably only has a few methods defined and “dd” might use something that is not supported. This is just speculation for /proc/pid/mem as I don’t have any proof. For /proc/pid/pagemap, see the following LXR link for its operations:
http://lxr.linux.no/linux+v2.6.39/fs/proc/task_mmu.c#L854
It would be interesting to see if anyone has a method that works directly from a command line and doesn’t require a script or C program.