We can inspect CPU registers in a context of a running program. We will compile a simple C program and we will load the binary in gdb command line debugger. Then, we will set a breakpoint in the program. Breakpoint can be thought of halting an executing program. When we run our program, the execution will completely halt at the breakpoint. At that time, we will inspect what our CPU registers contain.
gdb’s full name is GNU debugger which comes by default in Ubuntu (and in most unix flavors). For more information on gdb, you can read gdb’s wiki page.
Let’s make a very simple C program that prints “Hello World”. I saved this program as registers.c.
#include<stdio.h> #include<stdio.h> void main() { printf("Hello world\n"); }
Now let’s compile our program.
gcc registers.c -o registers
Make sure the program doesn’t have any errors by running it by command “./registers”
Let’s load the program in gdb. This can be done by command: “gdb binary_name”.
You should see the gdb console like this.
hardik@admin-VM:~/Desktop/info$ gdb registers GNU gdb (Ubuntu 7.11.1-0ubuntu1~16.5) 7.11.1 Copyright (C) 2016 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type "show copying" and "show warranty" for details. This GDB was configured as "i686-linux-gnu". Type "show configuration" for configuration details. For bug reporting instructions, please see: <http://www.gnu.org/software/gdb/bugs/>. Find the GDB manual and other documentation resources online at: <http://www.gnu.org/software/gdb/documentation/>. For help, type "help". Type "apropos word" to search for commands related to "word"... Reading symbols from registers...(no debugging symbols found)...done. (gdb)
First, we need to setup a breakpoint in the program. Breakpoint can be setup by specifying a function name, a line number, or an address of an instruction. We will set a breakpoint at main.
(gdb) break main Breakpoint 1 at 0x8048419 (gdb)
We have our breakpoint. Now if we run the program, program will halt at main. We will execute our program by typing “run”. At this point we can inspect our CPU registers by typing “info registers”.
(gdb) run Starting program: /home/hardik/Desktop/info/registers Breakpoint 1, 0x08048419 in main () (gdb) info registers eax 0xb7fb8dbc -1208250948 ecx 0xbffff050 -1073745840 edx 0xbffff074 -1073745804 ebx 0x0 0 esp 0xbffff034 0xbffff034 ebp 0xbffff038 0xbffff038 esi 0xb7fb7000 -1208258560 edi 0xb7fb7000 -1208258560 eip 0x8048419 0x8048419 <main+14> eflags 0x282 [ SF IF ] cs 0x73 115 ss 0x7b 123 ds 0x7b 123 es 0x7b 123 fs 0x0 0 gs 0x33 51 (gdb)
You can see that gdb will show values in all general purpose registers,eflags, eip and segment registers.
You can also read individual registers by “display /x register” command. “/x” will show the register value in hex format.
(gdb) display /x $eax 3: /x $eax = 0xb7fb8dbc (gdb) display /x $ax 4: /x $ax = 0x8dbc (gdb) display /x $al 5: /x $al = 0xbc (gdb) display /x $esp 6: /x $esp = 0xbffff034 (gdb) display /x $cx 7: /x $cx = 0xf050 (gdb)
Also, note that gdb makes us easy for us to see which flags are enabled in eflags register. More info on eflags register can be found here. For our program, SF and IF flags are enabled.
By default, gdb will now show all the register (FPU, XMM). To check all the registers we can type “info all-registers”.
(gdb) info all-registers eax 0xb7fb8dbc -1208250948 ecx 0xbffff050 -1073745840 edx 0xbffff074 -1073745804 ebx 0x0 0 esp 0xbffff034 0xbffff034 ebp 0xbffff038 0xbffff038 esi 0xb7fb7000 -1208258560 edi 0xb7fb7000 -1208258560 eip 0x8048419 0x8048419 <main+14> eflags 0x282 [ SF IF ] cs 0x73 115 ss 0x7b 123 ds 0x7b 123 es 0x7b 123 fs 0x0 0 gs 0x33 51 st0 0 (raw 0x00000000000000000000) st1 0 (raw 0x00000000000000000000) st2 0 (raw 0x00000000000000000000) st3 0 (raw 0x00000000000000000000) st4 0 (raw 0x00000000000000000000) st5 0 (raw 0x00000000000000000000) st6 0 (raw 0x00000000000000000000) st7 0 (raw 0x00000000000000000000) fctrl 0x37f 895 fstat 0x0 0 ftag 0xffff 65535 fiseg 0x0 0 fioff 0x0 0 foseg 0x0 0 fooff 0x0 0 fop 0x0 0 mxcsr 0x1f80 [ IM DM ZM OM UM PM ] ymm0 {v8_float = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, v4_double = {0x0, 0x0, 0x0, 0x0}, v32_int8 = {0x0 <repeats 32 times>}, v16_int16 = {0x0 <repeats 16 times>}, v8_int32 = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, v4_int64 = {0x0, 0x0, 0x0, 0x0}, v2_int128 = { 0x00000000000000000000000000000000, 0x00000000000000000000000000000000}} ---Type <return> to continue, or q <return> to quit--- ymm1 {v8_float = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, v4_double = {0x0, 0x0, 0x0, 0x0}, v32_int8 = {0x0 <repeats 32 times>}, v16_int16 = {0x0 <repeats 16 times>}, v8_int32 = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, v4_int64 = { 0x0, 0x0, 0x0, 0x0}, v2_int128 = {0x00000000000000000000000000000000, 0x00000000000000000000000000000000}} ymm2 {v8_float = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, v4_double = {0x0, 0x0, 0x0, 0x0}, v32_int8 = {0x0 <repeats 32 times>}, v16_int16 = {0x0 <repeats 16 times>}, v8_int32 = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, v4_int64 = { 0x0, 0x0, 0x0, 0x0}, v2_int128 = {0x00000000000000000000000000000000, 0x00000000000000000000000000000000}} ymm3 {v8_float = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, v4_double = {0x0, 0x0, 0x0, 0x0}, v32_int8 = {0x0 <repeats 32 times>}, v16_int16 = {0x0 <repeats 16 times>}, v8_int32 = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, v4_int64 = { 0x0, 0x0, 0x0, 0x0}, v2_int128 = {0x00000000000000000000000000000000, 0x00000000000000000000000000000000}} ymm4 {v8_float = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, v4_double = {0x0, 0x0, 0x0, 0x0}, v32_int8 = {0x0 <repeats 32 times>}, v16_int16 = {0x0 <repeats 16 times>}, v8_int32 = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, v4_int64 = { 0x0, 0x0, 0x0, 0x0}, v2_int128 = {0x00000000000000000000000000000000, 0x00000000000000000000000000000000}} ymm5 {v8_float = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, v4_double = {0x0, 0x0, 0x0, 0x0}, v32_int8 = {0x0 <repeats 32 times>}, v16_int16 = {0x0 <repeats 16 times>}, v8_int32 = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, v4_int64 = { 0x0, 0x0, 0x0, 0x0}, v2_int128 = {0x00000000000000000000000000000000, 0x00000000000000000000000000000000}} ymm6 {v8_float = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, v4_double = {0x0, 0x0, 0x0, 0x0}, v32_int8 = {0x0 <repeats 32 times>}, v16_int16 = {0x0 <repeats 16 times>}, v8_int32 = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, v4_int64 = { 0x0, 0x0, 0x0, 0x0}, v2_int128 = {0x00000000000000000000000000000000, 0x00000000000000000000000000000000}} ymm7 {v8_float = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, v4_double = {0x0, 0x0, 0x0, 0x0}, v32_int8 = {0x0 <repeats 32 times>}, v16_int16 = {0x0 <repeats 16 times>}, v8_int32 = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, v4_int64 = { 0x0, 0x0, 0x0, 0x0}, v2_int128 = {0x00000000000000000000000000000000, 0x00000000000000000000000000000000}} mm0 {uint64 = 0x0, v2_int32 = {0x0, 0x0}, v4_int16 = {0x0, 0x0, 0x0, 0x0}, v8_int8 = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}} mm1 {uint64 = 0x0, v2_int32 = {0x0, 0x0}, v4_int16 = {0x0, 0x0, 0x0, 0x0}, v8_int8 = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}} mm2 {uint64 = 0x0, v2_int32 = {0x0, 0x0}, v4_int16 = {0x0, 0x0, 0x0, 0x0}, v8_int8 = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}} mm3 {uint64 = 0x0, v2_int32 = {0x0, 0x0}, v4_int16 = {0x0, 0x0, 0x0, 0x0}, v8_int8 = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}} mm4 {uint64 = 0x0, v2_int32 = {0x0, 0x0}, v4_int16 = {0x0, 0x0, 0x0, 0x0}, v8_int8 = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}} mm5 {uint64 = 0x0, v2_int32 = {0x0, 0x0}, v4_int16 = {0x0, 0x0, 0x0, 0x0}, v8_int8 = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}} mm6 {uint64 = 0x0, v2_int32 = {0x0, 0x0}, v4_int16 = {0x0, 0x0, 0x0, 0x0}, v8_int8 = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}} mm7 {uint64 = 0x0, v2_int32 = {0x0, 0x0}, v4_int16 = {0x0, 0x0, 0x0, 0x0}, v8_int8 = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}} (gdb)
The assembly code can be shown in either Intel OR AT&T Syntax. By default, Ubuntu will show AT&T syntax. But I like Intel syntax so I will change that by “set disassembly-flavor intel” command in gdb console.
Now, if you type “disassemble” in gdb console, you will see disassembly in Intel syntax.
(gdb) set disassembly-flavor intel (gdb) disassemble Dump of assembler code for function main: 0x0804840b <+0>: lea ecx,[esp+0x4] 0x0804840f <+4>: and esp,0xfffffff0 0x08048412 <+7>: push DWORD PTR [ecx-0x4] 0x08048415 <+10>: push ebp 0x08048416 <+11>: mov ebp,esp 0x08048418 <+13>: push ecx => 0x08048419 <+14>: sub esp,0x4 0x0804841c <+17>: sub esp,0xc 0x0804841f <+20>: push 0x80484c0 0x08048424 <+25>: call 0x80482e0 <puts@plt> 0x08048429 <+30>: add esp,0x10 0x0804842c <+33>: nop 0x0804842d <+34>: mov ecx,DWORD PTR [ebp-0x4] 0x08048430 <+37>: leave 0x08048431 <+38>: lea esp,[ecx-0x4] 0x08048434 <+41>: ret End of assembler dump. (gdb)
Good job here. Neat explanation.
LikeLike