Stack pointer (rsp) always points to top of the stack. Register rsp changed on instructions push, pop, call, ret.
Instruction push put a value onto the stack. Instruction pop remove a value from the stack.
File main.s
.section .text
.global _start
.text
_start:
movabs $0xabcdabcdabcdabcd, %rax
push %rax
movabs $0x1234123412341234, %rax
push %rax
movq $60, %rax # syscall: exit
xorq %rdi, %rdi # exit code 0
syscallBuild.
as main.s -o main.o
ld main.o -o mainDebug.
(gdb) file main
(gdb) break _start
(gdb) run
Breakpoint 1, 0x0000000000401000 in _start ()Monitor stack.
(gdb) disassemble
Dump of assembler code for function _start:
=> 0x0000000000401000 <+0>: movabs $0xabcdabcdabcdabcd,%rax
0x000000000040100a <+10>: push %rax
0x000000000040100b <+11>: movabs $0x1234123412341234,%rax
0x0000000000401015 <+21>: push %rax
0x0000000000401016 <+22>: mov $0x3c,%rax
0x000000000040101d <+29>: xor %rdi,%rdi
0x0000000000401020 <+32>: syscall
(gdb) while 1
> x/4gx $rsp
> x/i $rip
> nexti
> end0x7fffffffe700: 0x0000000000000001 0x00007fffffffe9ab
0x7fffffffe710: 0x0000000000000000 0x00007fffffffe9bc
=> 0x401000 <_start>: movabs $0xabcdabcdabcdabcd,%rax
=> 0x40100a <_start+10>: push %rax
0x7fffffffe6f8: 0xabcdabcdabcdabcd 0x0000000000000001
0x7fffffffe708: 0x00007fffffffe9ab 0x0000000000000000
=> 0x40100b <_start+11>: movabs $0x1234123412341234,%rax
=> 0x401015 <_start+21>: push %rax
0x7fffffffe6f0: 0x1234123412341234 0xabcdabcdabcdabcd
0x7fffffffe700: 0x0000000000000001 0x00007fffffffe9abIllustrate.
┌───────────────┬───────────────────┐ ┌───────────────┬───────────────────┐ ┌───────────────┬───────────────────┐
│ address │ value │ │ address │ value │ │ address │ value │
├───────────────┼───────────────────┤ ├───────────────┼───────────────────┤ ├───────────────┼───────────────────┤
│ │ │ │ │ │ │ │ │
│ │ │ push │ │ │ push │0x7fffffffe6f0 │ 0x1234123412341234│
│ │ │ ─────► │0x7fffffffe6f8 │ 0xabcdabcdabcdabcd│ ─────► │0x7fffffffe6f8 │ 0xabcdabcdabcdabcd│
│0x7fffffffe700 │ 0x0000000000000001│ │0x7fffffffe700 │ 0x0000000000000001│ │0x7fffffffe700 │ 0x0000000000000001│
│0x7fffffffe708 │ 0x00007fffffffe9ab│ │0x7fffffffe708 │ 0x00007fffffffe9ab│ │0x7fffffffe708 │ 0x00007fffffffe9ab│
│0x7fffffffe710 │ 0x0000000000000000│ │0x7fffffffe710 │ 0x0000000000000000│ │0x7fffffffe710 │ 0x0000000000000000│
│0x7fffffffe718 │ 0x00007fffffffe9bc│ │0x7fffffffe718 │ 0x00007fffffffe9bc│ │0x7fffffffe718 │ 0x00007fffffffe9bc│
└───────────────┴───────────────────┘ └───────────────┴───────────────────┘ └───────────────┴───────────────────┘Instruction call pushes the address of the next instruction (rip) onto the stack then jump to the target function. This address is also called the return address. Instruction ret pops the return address from the stack and jump to this address.
File main.s
.section .text
.global _start
_start:
mov $5, %rdi # x = 5
mov $7, %rsi # y = 7
call sum # sum(x, y)
mov $60, %rax # syscall: exit
xor %rdi, %rdi # exit code 0
syscall
# add two integers
sum:
add %rsi, %rdi # x += y
mov %rdi, %rax # rax = x
retas main.s -o main.o
ld main.o -o main(gdb) file main
(gdb) break _start
(gdb) run
Breakpoint 1, 0x0000000000401000 in _start ()Monitor stack.
(gdb) disassemble
Dump of assembler code for function _start:
=> 0x0000000000401000 <+0>: mov $0x5,%rdi
0x0000000000401007 <+7>: mov $0x7,%rsi
0x000000000040100e <+14>: call 0x40101f <sum>
0x0000000000401013 <+19>: mov $0x3c,%rax
0x000000000040101a <+26>: xor %rdi,%rdi
0x000000000040101d <+29>: syscall
(gdb) while 1
> x/4gx $rsp
> x/i $rip
> stepi
> end0x7fffffffe700: 0x0000000000000001 0x00007fffffffe9ab
0x7fffffffe710: 0x0000000000000000 0x00007fffffffe9bc
=> 0x40100e <_start+14>: call 0x40101f <sum>
0x7fffffffe6f8: 0x0000000000401013 0x0000000000000001
0x7fffffffe708: 0x00007fffffffe9ab 0x0000000000000000
=> 0x401025 <sum+6>: ret
0x7fffffffe700: 0x0000000000000001 0x00007fffffffe9ab
0x7fffffffe710: 0x0000000000000000 0x00007fffffffe9bcIllustrate.
┌───────────────┬───────────────────┐ ┌───────────────┬───────────────────┐ ┌───────────────┬───────────────────┐
│ address │ value │ │ address │ value │ │ address │ value │
├───────────────┼───────────────────┤ ├───────────────┼───────────────────┤ ├───────────────┼───────────────────┤
│ │ │ │ │ │ │ │ │
│ │ │ call │ │ │ ret │ │ │
│ │ │ ─────► │0x7fffffffe6f8 │ 0x0000000000401013│ ─────► │ │ │
│0x7fffffffe700 │ 0x0000000000000001│ │0x7fffffffe700 │ 0x0000000000000001│ │0x7fffffffe700 │ 0x0000000000000001│
│0x7fffffffe708 │ 0x00007fffffffe9ab│ │0x7fffffffe708 │ 0x00007fffffffe9ab│ │0x7fffffffe708 │ 0x00007fffffffe9ab│
│0x7fffffffe710 │ 0x0000000000000000│ │0x7fffffffe710 │ 0x0000000000000000│ │0x7fffffffe710 │ 0x0000000000000000│
│0x7fffffffe718 │ 0x00007fffffffe9bc│ │0x7fffffffe718 │ 0x00007fffffffe9bc│ │0x7fffffffe718 │ 0x00007fffffffe9bc│
└───────────────┴───────────────────┘ └───────────────┴───────────────────┘ └───────────────┴───────────────────┘Base pointer (rbp) stores the start address of the function stack frame. Remains fixed throughout the function, allowing compiler to use its stable position to generate offsets for local variables and function arguments.
When calling a new C/C++ function, prolog is the compiler-generated code that sets up the stack frame for the new function. When leaving a C/C++ function, epilog is the compiler-generated code that restores the parent’s stack frame and jump back to parent function.
Example: parent_func calls child_func.
parent_func() {
child_func()
}
child_func() {
}<parent_func>:
call child_func # save parent next instruction address (rip) to stack,
# jump to child_func.<child_func>:
# prolog
push %rbp # save parent base pointer (rbp) to stack.
mov %rsp, %rbp # move current base pointer to top of stack (rsp).
... # function body
# epilog
pop %rbp # restore parent base pointer from stack.
ret # restore parent next instruction address from stack,
# jump to this instruction (parent_func).Illustrate.
┌───────────┐ ┌───────────┐ ┌───────────┐ ┌───────────┐
│ stack │ │ stack │ │ stack │ │ stack │
$rbp ─┐ ├───────────┤ ├───────────┤ $rbp ─┐ ├───────────┤ $rbp ──┐ ├───────────┤
│ │ │ │ │ │ │ │ │ │ │
│ │ │ │ │ │ │ │ │ │ │
│ │ │ │ │ │ │ │ │ │ │
│ │ │ parent_func │ │ └─►│parent $rbp│ │ │ │
│ │ │ call │parent $rip│ │parent $rip│ │ │ │
└─┼► │ child_func │ │ prolog │ │ epilog └─┼► │
│ │ ───────────► │ │ ──────────► │ │ ──────────► │ │
└───────────┘ └───────────┘ └───────────┘ └───────────┘Local variables locates on the stack for each function call. Their addresses are based on negative offsets from the base pointer rbp.
int a, b, c;
a = 1;
b = 2;
c = 3;
movl $0x1,-0xc(%rbp) ; a at $rbp-0xc
movl $0x2,-0x8(%rbp) ; b at $rbp-0x8
movl $0x3,-0x4(%rbp) ; c at $rbp-0x4When the parent function calls the child function, the arguments are saved in the registers rdi, rsi, rdx, rcx, r8, and r9. If more than 6 arguments, the extra arguments are saved on the stack. Inside the child function, it reads the registers and saves the arguments into its stack frame. The argument addresses in the stack frame are based on the base pointer rbp.
parent_function()
{
child_func(a, b);
}
int child_func(int a, int b)
{
}<parent_function>:
mov -0x8(%rbp),%esi # save a to $esi
mov -0xc(%rbp),%edi # save b to $edi
call child_func
<child_func>:
mov %edi,-0x14(%rbp) # save $edi to stack, b
mov %esi,-0x18(%rbp) # save $esi to stack, aInstruction call pushes the parent rip onto the stack. Prolog pushes the parent rip onto the stack. Function arguments and local variables are stored in the stack, and their addresses are based on the current $rbp.
So, from current rbp, go backward to lower addresses, we reach the local variables and arguments. From current rbp, go forward to higher addresses, we reach the parent rbp and rip.
In GDB:
x/-6wx $rbp.x/2a $rbp.───low address ┌────────────────┐
│ stack │
├────────────────┤
│ │
│ ... │
│ argument │
│ argument │
│ ... │
│ local variable │
│ local variable │
$rbp ───► │ parent $rbp │
│ parent %rip │
│ │
──high address └────────────────┘int func(int argv1, int argv2)
{
int a = 0xaaaaaaaa;
int b = 0xbbbbbbbb;
int c = 0xcccccccc;
int d = 0xdddddddd;
return 0;
}
int main(int argc, char** argv)
{
func(0x11111111, 0x22222222);
return 0;
}$ gcc -g main.c -o main(gdb) file main
(gdb) disassemble func
0x0000000000001129 <+0>: endbr64
0x000000000000112d <+4>: push %rbp
0x000000000000112e <+5>: mov %rsp,%rbp
0x0000000000001131 <+8>: mov %edi,-0x14(%rbp)
0x0000000000001134 <+11>: mov %esi,-0x18(%rbp)
0x0000000000001137 <+14>: movl $0xaaaaaaaa,-0x10(%rbp)
0x000000000000113e <+21>: movl $0xbbbbbbbb,-0xc(%rbp)
0x0000000000001145 <+28>: movl $0xcccccccc,-0x8(%rbp)
0x000000000000114c <+35>: movl $0xdddddddd,-0x4(%rbp)
0x0000000000001153 <+42>: mov $0x0,%eax
0x0000000000001158 <+47>: pop %rbp
0x0000000000001159 <+48>: ret(gdb) break *func+42
(gdb) run
Breakpoint 1, 0x0000555555555153 in func ()(gdb) x/-6wx $rbp
0x7fffffffe5b8: 0x22222222 0x11111111 0xaaaaaaaa 0xbbbbbbbb
0x7fffffffe5c8: 0xcccccccc 0xdddddddd(gdb) x/2a $rbp
0x7fffffffe5d0: 0x7fffffffe5f0 0x55555555517c <main+34>Illustrate the stack.
┌────────────────┐
│ stack │
───low address ├────────────────┤
│ │
0x7fffffffe5b8 │ 0x22222222 │ argument argv2
│ 0x11111111 │ argument argv2
│ 0xaaaaaaaa │ variable a
│ 0xbbbbbbbb │ variable b
0x7fffffffe5c8 │ 0xcccccccc │ variable c
│ 0xdddddddd │ variable d
$rbp ───► 0x7fffffffe5d0 │ 0x7fffffffe5f0 │ parent $rbp
│ 0x55555555517c │ parent $rip
│ │
──high address └────────────────┘A Canonical Frame Address (CFA) is a fixed address in a stack frame. CFA is used as a pivot to locate return addresses, function arguments and local variables.
CFA rules define how to compute the Canonical Frame Address at each instruction in a function.
$ readelf --debug-dump=frames-interp main
Contents of the .eh_frame section:
00000070 000000000000001c 00000074 FDE cie=00000000 pc=0000000000001129..000000000000115a
LOC CFA rbp ra
0000000000001129 rsp+8 u c-8
000000000000112e rsp+16 c-16 c-8
0000000000001131 rbp+16 c-16 c-8
0000000000001159 rsp+8 c-16 c-8In the below objdump output, range of func() is from
0x1129 to 0x1159. Use this range to find the
corresponding block in readelf output, we have
pc=0000000000001129..000000000000115a.
So, CFA rules in func():
1129: cfa = rsp + 8112e: cfa = rsp + 161131: cfa = rbp + 161159: cfa = rsp + 8$ objdump --disassemble --no-show-raw-insn main
0000000000001129 <func>:
1129: endbr64
112d: push %rbp
112e: mov %rsp,%rbp
1131: mov %edi,-0x14(%rbp)
1134: mov %esi,-0x18(%rbp)
1137: movl $0xaaaaaaaa,-0x10(%rbp)
113e: movl $0xbbbbbbbb,-0xc(%rbp)
1145: movl $0xcccccccc,-0x8(%rbp)
114c: movl $0xdddddddd,-0x4(%rbp)
1153: mov $0x0,%eax
1158: pop %rbp
1159: retLine-by-line explanation.
0000000000001129 <func>:
1129: initially, assign cfa: cfa = rsp + 8
112d: push instruction moves stack pointer down: rsp = rsp - 8
112e: to keep cfa point to the assigned address: cfa = rsp + 16
1131: switch cfa rule to base pointer: cfa = rbp + 16
1134:
1137:
113e:
1145:
114c:
1153:
1158: pop instruction moves stack pointer up: rsp = rsp + 8
1159: to keep cfa point to the assigned address: cfa = rsp + 8(gdb) set print frame-info location-and-address
(gdb) break *func+0
Breakpoint 1 at 0x1129: file main.c, line 2.
(gdb) run
Starting program: /root/demo/main
Breakpoint 1, 0x0000555555555129 in func (argv1=0, argv2=0) at main.c:2(gdb) disassemble
Dump of assembler code for function func:
=> 0x0000555555555129 <+0>: endbr64
0x000055555555512d <+4>: push %rbp
0x000055555555512e <+5>: mov %rsp,%rbp
0x0000555555555131 <+8>: mov %edi,-0x14(%rbp)
0x0000555555555134 <+11>: mov %esi,-0x18(%rbp)
0x0000555555555137 <+14>: movl $0xaaaaaaaa,-0x10(%rbp)
0x000055555555513e <+21>: movl $0xbbbbbbbb,-0xc(%rbp)
0x0000555555555145 <+28>: movl $0xcccccccc,-0x8(%rbp)
0x000055555555514c <+35>: movl $0xdddddddd,-0x4(%rbp)
0x0000555555555153 <+42>: mov $0x0,%eax
0x0000555555555158 <+47>: pop %rbp
0x0000555555555159 <+48>: ret
End of assembler dump.
(gdb) print $rsp
$1 = (void *) 0x7fffffffe5d8
(gdb) print $rsp + 8
$2 = (void *) 0x7fffffffe5e0cfa = rsp + 8 = 0x7fffffffe5e0(gdb) watch $rsp
Watchpoint 2: $rsp
(gdb) watch $rbp
Watchpoint 3: $rbp(gdb) continue
Continuing.
Watchpoint 2: $rsp
Old value = (void *) 0x7fffffffe5d8
New value = (void *) 0x7fffffffe5d0
0x000055555555512e in func (argv1=0, argv2=0) at main.c:2After push instruction, rsp moves down from
0x7fffffffe5d8 to 0x7fffffffe5d0.
(gdb) disassemble
Dump of assembler code for function func:
0x0000555555555129 <+0>: endbr64
0x000055555555512d <+4>: push %rbp
=> 0x000055555555512e <+5>: mov %rsp,%rbp
0x0000555555555131 <+8>: mov %edi,-0x14(%rbp)
0x0000555555555134 <+11>: mov %esi,-0x18(%rbp)
0x0000555555555137 <+14>: movl $0xaaaaaaaa,-0x10(%rbp)
0x000055555555513e <+21>: movl $0xbbbbbbbb,-0xc(%rbp)
0x0000555555555145 <+28>: movl $0xcccccccc,-0x8(%rbp)
0x000055555555514c <+35>: movl $0xdddddddd,-0x4(%rbp)
0x0000555555555153 <+42>: mov $0x0,%eax
0x0000555555555158 <+47>: pop %rbp
0x0000555555555159 <+48>: ret
End of assembler dump.
(gdb) print $rsp + 16
$3 = (void *) 0x7fffffffe5e0cfa = rsp + 16 = 0x7fffffffe5e0(gdb) continue
Continuing.
Watchpoint 3: $rbp
Old value = (void *) 0x7fffffffe5f0
New value = (void *) 0x7fffffffe5d0
0x0000555555555131 in func (argv1=0, argv2=0) at main.c:2The rbp changes from 0x7fffffffe5f0 to
0x7fffffffe5d0.
(gdb) disassemble
Dump of assembler code for function func:
0x0000555555555129 <+0>: endbr64
0x000055555555512d <+4>: push %rbp
0x000055555555512e <+5>: mov %rsp,%rbp
=> 0x0000555555555131 <+8>: mov %edi,-0x14(%rbp)
0x0000555555555134 <+11>: mov %esi,-0x18(%rbp)
0x0000555555555137 <+14>: movl $0xaaaaaaaa,-0x10(%rbp)
0x000055555555513e <+21>: movl $0xbbbbbbbb,-0xc(%rbp)
0x0000555555555145 <+28>: movl $0xcccccccc,-0x8(%rbp)
0x000055555555514c <+35>: movl $0xdddddddd,-0x4(%rbp)
0x0000555555555153 <+42>: mov $0x0,%eax
0x0000555555555158 <+47>: pop %rbp
0x0000555555555159 <+48>: ret
End of assembler dump.
(gdb) print $rbp + 16
$4 = (void *) 0x7fffffffe5e0cfa = rbp + 16 = 0x7fffffffe5e0(gdb) continue
Continuing.
Watchpoint 2: $rsp
Old value = (void *) 0x7fffffffe5d0
New value = (void *) 0x7fffffffe5d8
Watchpoint 3: $rbp
Old value = (void *) 0x7fffffffe5d0
New value = (void *) 0x7fffffffe5f0
0x0000555555555159 in func (argv1=286331153, argv2=572662306) at main.c:8After pop instruction, rsp changes from 0x7fffffffe5d0
to 0x7fffffffe5d8.
(gdb) disassemble
Dump of assembler code for function func:
0x0000555555555129 <+0>: endbr64
0x000055555555512d <+4>: push %rbp
0x000055555555512e <+5>: mov %rsp,%rbp
0x0000555555555131 <+8>: mov %edi,-0x14(%rbp)
0x0000555555555134 <+11>: mov %esi,-0x18(%rbp)
0x0000555555555137 <+14>: movl $0xaaaaaaaa,-0x10(%rbp)
0x000055555555513e <+21>: movl $0xbbbbbbbb,-0xc(%rbp)
0x0000555555555145 <+28>: movl $0xcccccccc,-0x8(%rbp)
0x000055555555514c <+35>: movl $0xdddddddd,-0x4(%rbp)
0x0000555555555153 <+42>: mov $0x0,%eax
0x0000555555555158 <+47>: pop %rbp
=> 0x0000555555555159 <+48>: ret
End of assembler dump.
(gdb) print $rsp + 8
$5 = (void *) 0x7fffffffe5e0cfa = rsp + 8 = 0x7fffffffe5e0So, within func(), when rsp or rbp changes, the CFA rule is adjusted
to keep CFA point to the fixed assigned address
0x7fffffffe5e0.
The Debugging Information Entry (DIE) is used to translate assembly code back to C/C++ code.
Each DIE entity consists of:
$ readelf --debug-dump=info main
<1><6d>: Abbrev Number: 6 (DW_TAG_base_type) # Offset 6d, entry describes a data type.
<6e> DW_AT_byte_size : 4 # Size: 4
<70> DW_AT_name : int # Data type name is int
<1><85>: Abbrev Number: 8 (DW_TAG_subprogram) # Level 1, entry describes a function.
<86> DW_AT_name : (indirect string, offset: 0x0): func # Name: func().
<91> DW_AT_low_pc : 0x1129 # Low address: 0x1129.
<99> DW_AT_high_pc : 0x31 # Length: 0x31.
<2><a3>: Abbrev Number: 1 (DW_TAG_formal_parameter) # Level 2, entry describes a function argument of func().
<a4> DW_AT_name : (indirect string, offset: 0x5): argv1 # Name: argv1
<aa> DW_AT_type : <0x6d> # Type is defined at offset 0x6d, which is int.
<ae> DW_AT_location : 2 byte block: 91 5c (DW_OP_fbreg: -36) # Frame-based location, location = CFA - 36.
<2><bf>: Abbrev Number: 2 (DW_TAG_variable) # Level 2, entry describes a local variable within func().
<c0> DW_AT_name : a # Name: a
<c3> DW_AT_type : <0x6d> # Type is defined at offset 0x6d, which is int.
<c7> DW_AT_location : 2 byte block: 91 60 (DW_OP_fbreg: -32) # Frame-based location, location = CBA - 32To reverse a function argument or a local variable at an instruction:
Apply the procedure to reverse the argv1 and argv2 arguments of func().
$ objdump --disassemble --no-show-raw-insn main
0000000000001129 <func>:
1129: endbr64
112d: push %rbp
112e: mov %rsp,%rbp
1131: mov %edi,-0x14(%rbp)
1134: mov %esi,-0x18(%rbp)
1137: movl $0xaaaaaaaa,-0x10(%rbp)
113e: movl $0xbbbbbbbb,-0xc(%rbp)
1145: movl $0xcccccccc,-0x8(%rbp)
114c: movl $0xdddddddd,-0x4(%rbp)
==> 1153: mov $0x0,%eax
1158: pop %rbp
1159: ret$ readelf --debug-dump=frames-interp main
Contents of the .eh_frame section:
00000070 000000000000001c 00000074 FDE cie=00000000 pc=0000000000001129..000000000000115a
LOC CFA rbp ra
0000000000001129 rsp+8 u c-8
000000000000112e rsp+16 c-16 c-8
0000000000001131 rbp+16 c-16 c-8 <==
0000000000001159 rsp+8 c-16 c-8$ readelf --debug-dump=info main
Contents of the .debug_info section:
<1><85>: Abbrev Number: 8 (DW_TAG_subprogram)
<86> DW_AT_name : (indirect string, offset: 0x0): func
<91> DW_AT_low_pc : 0x1129
<2><a3>: Abbrev Number: 1 (DW_TAG_formal_parameter)
<a4> DW_AT_name : (indirect string, offset: 0x5): argv1
<ae> DW_AT_location : 2 byte block: 91 5c (DW_OP_fbreg: -36)
<2><b1>: Abbrev Number: 1 (DW_TAG_formal_parameter)
<b2> DW_AT_name : (indirect string, offset: 0x9e): argv2
<bc> DW_AT_location : 2 byte block: 91 58 (DW_OP_fbreg: -40)Location of argv1:
DW_AT_location = DW_OP_fbreg - 36
= cfa - 36
= (rbp + 16) - 36
= rbp - 20
Location of argv2:
DW_AT_location = DW_OP_fbreg - 40
= cfa - 40
= (rbp + 16) - 40
= rbp - 24
So,
argv1 locates at (rbp - 20)
argv2 locates at (rbp - 24)$ gdb main
(gdb) disassemble func
Dump of assembler code for function func:
0x0000000000001129 <+0>: endbr64
0x000000000000112d <+4>: push %rbp
0x000000000000112e <+5>: mov %rsp,%rbp
0x0000000000001131 <+8>: mov %edi,-0x14(%rbp)
0x0000000000001134 <+11>: mov %esi,-0x18(%rbp)
0x0000000000001137 <+14>: movl $0xaaaaaaaa,-0x10(%rbp)
0x000000000000113e <+21>: movl $0xbbbbbbbb,-0xc(%rbp)
0x0000000000001145 <+28>: movl $0xcccccccc,-0x8(%rbp)
0x000000000000114c <+35>: movl $0xdddddddd,-0x4(%rbp)
0x0000000000001153 <+42>: mov $0x0,%eax
0x0000000000001158 <+47>: pop %rbp
0x0000000000001159 <+48>: ret
(gdb) break *func+42
(gdb) run
Breakpoint 1, 0x0000555555555153 in func (argv1=286331153, argv2=572662306) at main.c:7(gdb) disassemble
Dump of assembler code for function func:
0x0000555555555129 <+0>: endbr64
0x000055555555512d <+4>: push %rbp
0x000055555555512e <+5>: mov %rsp,%rbp
0x0000555555555131 <+8>: mov %edi,-0x14(%rbp)
0x0000555555555134 <+11>: mov %esi,-0x18(%rbp)
0x0000555555555137 <+14>: movl $0xaaaaaaaa,-0x10(%rbp)
0x000055555555513e <+21>: movl $0xbbbbbbbb,-0xc(%rbp)
0x0000555555555145 <+28>: movl $0xcccccccc,-0x8(%rbp)
0x000055555555514c <+35>: movl $0xdddddddd,-0x4(%rbp)
=> 0x0000555555555153 <+42>: mov $0x0,%eax
0x0000555555555158 <+47>: pop %rbp
0x0000555555555159 <+48>: ret
# print argv1
(gdb) x/wx $rbp-20
0x7fffffffe5bc: 0x11111111
# print argv2
(gdb) x/wx $rbp-24
0x7fffffffe5b8: 0x22222222To reverse local variables, we use the same steps as reverse function arguments.
Apply the procedure to reverse the a,b,c,d variables within func().
$ objdump --disassemble --no-show-raw-insn main
0000000000001129 <func>:
1129: endbr64
112d: push %rbp
112e: mov %rsp,%rbp
1131: mov %edi,-0x14(%rbp)
1134: mov %esi,-0x18(%rbp)
1137: movl $0xaaaaaaaa,-0x10(%rbp)
113e: movl $0xbbbbbbbb,-0xc(%rbp)
1145: movl $0xcccccccc,-0x8(%rbp)
114c: movl $0xdddddddd,-0x4(%rbp)
==> 1153: mov $0x0,%eax
1158: pop %rbp
1159: ret$ readelf --debug-dump=frames-interp main
Contents of the .eh_frame section:
00000070 000000000000001c 00000074 FDE cie=00000000 pc=0000000000001129..000000000000115a
LOC CFA rbp ra
0000000000001129 rsp+8 u c-8
000000000000112e rsp+16 c-16 c-8
0000000000001131 rbp+16 c-16 c-8 <==
0000000000001159 rsp+8 c-16 c-8$ readelf --debug-dump=info main
Contents of the .debug_info section:
<1><85>: Abbrev Number: 8 (DW_TAG_subprogram)
<86> DW_AT_name : (indirect string, offset: 0x0): func
<91> DW_AT_low_pc : 0x1129
<2><bf>: Abbrev Number: 2 (DW_TAG_variable)
<c0> DW_AT_name : a
<c7> DW_AT_location : 2 byte block: 91 60 (DW_OP_fbreg: -32)
<2><ca>: Abbrev Number: 2 (DW_TAG_variable)
<cb> DW_AT_name : b
<d2> DW_AT_location : 2 byte block: 91 64 (DW_OP_fbreg: -28)
<2><d5>: Abbrev Number: 2 (DW_TAG_variable)
<d6> DW_AT_name : c
<dd> DW_AT_location : 2 byte block: 91 68 (DW_OP_fbreg: -24)
<2><e0>: Abbrev Number: 2 (DW_TAG_variable)
<e1> DW_AT_name : d
<e8> DW_AT_location : 2 byte block: 91 6c (DW_OP_fbreg: -20)Location of a:
DW_AT_location = DW_OP_fbreg - 32
= cfa - 32
= (rbp + 16) - 32
= rbp - 16
Location of b:
DW_AT_location = DW_OP_fbreg - 28
= cfa - 28
= (rbp + 16) - 28
= rbp - 12
Location of c:
DW_AT_location = DW_OP_fbreg - 24
= cfa - 24
= (rbp + 16) - 24
= rbp - 8
Location of d:
DW_AT_location = DW_OP_fbreg - 20
= cfa - 20
= (rbp + 16) - 20
= rbp - 4
So,
a locates at (rbp - 16)
b locates at (rbp - 12)
c locates at (rbp - 8)
d locates at (rbp - 4)(gdb) break *func+42
(gdb) run
Breakpoint 1, 0x0000555555555153 in func (argv1=286331153, argv2=572662306) at main.c:7
(gdb) disassemble
Dump of assembler code for function func:
0x0000555555555129 <+0>: endbr64
0x000055555555512d <+4>: push %rbp
0x000055555555512e <+5>: mov %rsp,%rbp
0x0000555555555131 <+8>: mov %edi,-0x14(%rbp)
0x0000555555555134 <+11>: mov %esi,-0x18(%rbp)
0x0000555555555137 <+14>: movl $0xaaaaaaaa,-0x10(%rbp)
0x000055555555513e <+21>: movl $0xbbbbbbbb,-0xc(%rbp)
0x0000555555555145 <+28>: movl $0xcccccccc,-0x8(%rbp)
0x000055555555514c <+35>: movl $0xdddddddd,-0x4(%rbp)
=> 0x0000555555555153 <+42>: mov $0x0,%eax
0x0000555555555158 <+47>: pop %rbp
0x0000555555555159 <+48>: ret
# print variable a
(gdb) x/wx $rbp-16
0x7fffffffe5c0: 0xaaaaaaaa
# print variable b
(gdb) x/wx $rbp-12
0x7fffffffe5c4: 0xbbbbbbbb
# print variable c
(gdb) x/wx $rbp-8
0x7fffffffe5c8: 0xcccccccc
# print variable d
(gdb) x/wx $rbp-4
0x7fffffffe5cc: 0xddddddddPrint frame info.
(gdb) info frame
Stack level 0, frame at 0x7fffffffe5e0:
rip = 0x555555555153 in func (main.c:7); saved rip = 0x55555555517c
called by frame at 0x7fffffffe600
source language c.
Arglist at 0x7fffffffe5d0, args: argv1=286331153, argv2=572662306
Locals at 0x7fffffffe5d0, Previous frame's sp is 0x7fffffffe5e0
Saved registers:
rbp at 0x7fffffffe5d0, rip at 0x7fffffffe5d8Print function arguments and local variables.
(gdb) info args
argv1 = 286331153
argv2 = 572662306
(gdb) info locals
a = -1431655766
b = -1145324613
c = -858993460
d = -572662307