Determine if a specific process is 32- or 64-Bit
Solution 1
If you want to limit yourself to ELF detection, you can read the ELF header of /proc/$PID/exe
yourself. It's quite trivial: if the 5th byte in the file is 1, it's a 32-bit binary. If it's 2, it's 64-bit. For added sanity checking:
- If the first 5 bytes are
0x7f, "ELF", 1
: it's a 32 bit ELF binary. - If the first 5 bytes are
0x7f, "ELF", 2
: it's a 64 bit ELF binary. - Otherwise: it's inconclusive.
You could also use objdump
, but that takes away your libmagic
dependency and replaces it with a libelf
one.
Another way: you can also parse the /proc/$PID/auxv
file. According to proc(5)
:
This contains the contents of the ELF interpreter information passed to the process at exec time. The format is one unsigned long ID plus one unsigned long value for each entry. The last entry contains two zeros.
The meanings of the unsigned long
keys are in /usr/include/linux/auxvec.h
. You want AT_PLATFORM
, which is 0x00000f
. Don't quote me on that, but it appears the value should be interpreted as a char *
to get the string description of the platform.
You may find this StackOverflow question useful.
Yet another way: you can instruct the dynamic linker (man ld
) to dump information about the executable. It prints out to standard output the decoded AUXV structure. Warning: this is a hack, but it works.
LD_SHOW_AUXV=1 ldd /proc/$SOME_PID/exe | grep AT_PLATFORM | tail -1
This will show something like:
AT_PLATFORM: x86_64
I tried it on a 32-bit binary and got i686
instead.
How this works: LD_SHOW_AUXV=1
instructs the Dynamic Linker to dump the decoded AUXV structure before running the executable. Unless you really like to make your life interesting, you want to avoid actually running said executable. One way to load and dynamically link it without actually calling its main()
function is to run ldd(1)
on it. The downside: LD_SHOW_AUXV
is enabled by the shell, so you'll get dumps of the AUXV structures for: the subshell, ldd
, and your target binary. So we grep
for AT_PLATFORM, but only keep the last line.
Parsing auxv: if you parse the auxv
structure yourself (not relying on the dynamic loader), then there's a bit of a conundrum: the auxv
structure follows the rule of the process it describes, so sizeof(unsigned long)
will be 4 for 32-bit processes and 8 for 64-bit processes. We can make this work for us. In order for this to work on 32-bit systems, all key codes must be 0xffffffff
or less. On a 64-bit system, the most significant 32 bits will be zero. Intel machines are little endians, so these 32 bits follow the least significant ones in memory.
As such, all you need to do is:
1. Read 16 bytes from the `auxv` file.
2. Is this the end of the file?
3. Then it's a 64-bit process.
4. Done.
5. Is buf[4], buf[5], buf[6] or buf[7] non-zero?
6. Then it's a 32-bit process.
7. Done.
8. Go to 1.
Parsing the maps file: this was suggested by Gilles, but didn't quite work. Here's a modified version that does. It relies on reading the /proc/$PID/maps
file. If the file lists 64-bit addresses, the process is 64 bits. Otherwise, it's 32 bits. The problem lies in that the kernel will simplify the output by stripping leading zeroes from hex addresses in groups of 4, so the length hack can't quite work. awk
to the rescue:
if ! [ -e /proc/$pid/maps ]; then
echo "No such process"
else
case $(awk </proc/$pid/maps -- 'END { print substr($1, 0, 9); }') in
*-) echo "32 bit process";;
*[0-9A-Fa-f]) echo "64 bit process";;
*) echo "Insufficient permissions.";;
esac
fi
This works by checking the starting address of the last memory map of the process. They're listed like 12345678-deadbeef
. So, if the process is a 32-bit one, that address will be eight hex digits long, and the ninth will be a hyphen. If it's a 64-bit one, the highest address will be longer than that. The ninth character will be a hex digit.
Be aware: all but the first and last methods need Linux kernel 2.6.0 or newer, since the auxv
file wasn't there before.
Solution 2
Look in /proc/$pid/maps
. The address ranges are over 32-bit addresses (8 hexadecimal digits) or 64-bit addresses (16 hexadecimal digits). This works for any kind of executable, no matter what format. You can only get information about processes running as the same user (unless you're root).
if ! [ -e /proc/$pid/maps ]; then
echo No such process
elif grep -q '^........[^-]' /proc/$pid/maps; then
echo 64-bit
elif grep -q . /proc/$pid/maps; then
echo 32-bit
else
echo Insufficient permissions
fi
If you have no permission to access this file, then I think the only way is to try to analyze the executable. (While you can always read /proc/$pid/stat
, none of the fields that are shown for processes running as different users reveal the process's bit size.) You can make good guess of the process's executable with ps -o comm=
, and looking that up in the PATH
— but beware that the process may have been launched with a different PATH
, or may have rewritten its argv[0]
. You can then analyze the executable — if you're willing to assume ELF, look at the 5th byte.
Related videos on Youtube
![Flexo](https://i.stack.imgur.com/qxeUf.jpg?s=256&g=1)
Comments
-
Flexo almost 2 years
Given a 2.6.x or newer Linux kernel and existing userland that is capable of running both ELF32 and ELF64 binaries (i.e. well past How do I know that my CPU supports 64bit operating systems under Linux?) how can I determine if a given process (by PID) is running in 32- or 64-bit mode?
The naive solution would be to run:
file -L /proc/pid/exe | grep -o 'ELF ..-bit [LM]SB'
but is that information exposed directly in
/proc
without relying onlibmagic
? -
goldilocks over 10 yearsHmmm, I wonder if the ELF header is in
/proc/[pid]/auxv
: "the ELF interpreter information passed to the process at exec time. The format is one unsigned long ID plus one unsigned long value for each entry" (man proc
). -
Simon Gates over 10 yearsThe header itself isn't. I just
hd
ed one and it lacked the magic number. There may be some relevant information there, but I think it'd be subject to more frequent changes than the ELF header itself. It was also introduced in 2.6.0, so it's not quite as ubiquitous as/proc/PID/exe
. But it does have the architecture information. I'll update my answer. -
Netch over 10 yearsI've tested your recipe and it failed. OpenSuSE 12.2, x86-64, kernel 3.4.63-2.44-default, /bin/bash. The /proc/$pid/maps lines for the binary and the first heap are written in 32-bit style, but all others are in 64-bit style. Likely they are printed using "%08x", but anyway this recipe shall be adjusted.
-
Simon Gates over 10 yearsI'm getting a mixture of 8, 12 and 16-nybble values on all the boxes I tried it with. Without checking the source, my guess is the kernel adjusts the padding to the lowest multiple of 16-bits greater than the address range for each line printed, so you'd have to find the longest sequence of hex characters, then check.
-
Simon Gates over 10 yearsBUT, since the
vsyscall
map is always the highest, you could get away with just changinghead
totail
— which, sadly, won't work because proc doesn't implementseek(2)
, so it'll have to be something uglier, likeawk /proc/self/maps -- 'END { print substr($1, 0, 9); }'
-
Flexo over 10 yearsauxv turned out to be trickier than I'd hoped -
sizeof(unsigned long)
is 8 on 64 bit or 4 on 32 bit which means that to correctly interpret it directly you have to know if the process is 64 bit or 32 bit to begin with! -
Simon Gates over 10 yearsYou're absolutely right. That's pretty annoying. Quick heuristic: if bytes 16x+y (4≤y≤7) are all zero in the file, you're looking at a 64-bit executable. This is a kludge: I'm assuming a little endian machine, and that all the
auxv
key codes fit a 32-bitunsigned long
, so the most significant 32-bits on a 64-bit box would be zero.