[心得] 實做 c backtrace

作者: descent (「雄辯是銀,沉默是金」)   2024-04-28 11:06:25
c 的 backtrace 是在 call function 時, 如何得知誰 call 了這個 function。
c 的 backtrace 如何做到, 問 chatgpt 馬上就給出 list 1. 的範例程式, 真的好用,
以 list 2 來說:
_start -> __libc_start_main -> main -> f1 -> f2 -> f3 -> print_backtrace
會得到
xx [0x400b7b]
xx [0x400d60]
xx [0x401087]
xx [0x4010a4]
xx [0x4010c0]
xx [0x401689]
xx [0x400a5a]
使用 addr2line 可以查到對應的 function
addr2line -f -e t2 0x400b7b
print_backtrace
/home/descent/git/progs/backtrace/t2.c:15
addr2line -f -e t2 0x400a5a
_start
??:?
會得到: print_backtrace -> f3 -> f2 -> f1 -> main -> __libc_start_main ->
_start
但如果再問 chatgpt 要怎麼實做 backtrace(), backtrace_symbols(), 它就鬼打牆了。
所以如果不借助 backtrace(), backtrace_symbols() 要怎麼辦到呢?
這和平台有關, 本篇是在 x86_64 環境的實做。
list 1. print_backtrace()
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <stdint.h>
4
5 #include <execinfo.h>
6
7 #define BT_BUF_SIZE 100
8
9 void print_backtrace()
10 {
11 void *buffer[BT_BUF_SIZE];
12 char **strings;
13 int nptrs;
14
15 nptrs = backtrace(buffer, BT_BUF_SIZE);
16 printf("backtrace() returned %d addresses\n", nptrs);
17
18 strings = backtrace_symbols(buffer, nptrs);
19 if (strings == NULL) {
20 perror("backtrace_symbols");
21 exit(EXIT_FAILURE);
22 }
23
24 for (int i = 0; i < nptrs; i++) {
25 printf("xx %s\n", strings[i]);
26 }
27
28 free(strings);
29 }
list 2. t2.c
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <stdint.h>
4
5 #include <execinfo.h>
6
7 #define BT_BUF_SIZE 100
8
9 void print_backtrace()
10 {
11 void *buffer[BT_BUF_SIZE];
12 char **strings;
13 int nptrs;
14
15 nptrs = backtrace(buffer, BT_BUF_SIZE);
16 printf("backtrace() returned %d addresses\n", nptrs);
17
18 strings = backtrace_symbols(buffer, nptrs);
19 if (strings == NULL) {
20 perror("backtrace_symbols");
21 exit(EXIT_FAILURE);
22 }
23
24 for (int i = 0; i < nptrs; i++) {
25 printf("xx %s\n", strings[i]);
26 }
27
28 free(strings);
29 }
30
31 void f5()
32 {
33 printf("in f5\n");
34 print_backtrace();
35 }
36
37
38 #include <stdio.h>
39
40 uintptr_t get_rip_value()
41 {
42 //uintptr_t rip_value;
43 //asm volatile("movq $0, %%rax; movq (%%rsp), %%rax" : "=a"
(rip_value));
44 //asm("mov %%rip, %0" : "=r" (rip_value));
45 uintptr_t current_address;
46 asm("lea (%%rip), %0" : "=r" (current_address));
47 return current_address;
48 }
49
50 unsigned long get_rbp()
51 {
52 unsigned long rbp_value;
53 asm("movq %%rbp, %0" : "=r" (rbp_value));
54 printf("The value of RBP register is: %#lx\n", rbp_value);
55 return rbp_value;
56 }
57
58 int addr2func(uintptr_t addr)
59 {
60 char cmd[128] = {0};
61 sprintf(cmd, "addr2line -f -e t2 %#lx\n", addr);
62 printf("cmd: %s\n", cmd);
63 system(cmd);
64 return 0;
65 }
66
67 void f3()
68 {
69 print_backtrace();
70 #if 1
71 int level = 0;
72 printf("in f3\n");
73 //while(1)
74 {
75 {
76 uintptr_t current_address;
77 asm("lea (%%rip), %0" : "=r" (current_address));
78 printf("current_address : %#lx\n", current_address);
79 addr2func(current_address);
80
81 printf("======\n");
82
83 unsigned long rbp_value, previous_rbp;
84 asm("movq %%rbp, %0" : "=r" (rbp_value));
85 printf("%d ## The value of RBP register is: %#lx\n", level,
rbp_value);
86 ++level;
87
88 uintptr_t ret_addr = *(uintptr_t*)(rbp_value + sizeof(uintptr_t));
89 printf("ret_addr : %#lx\n", ret_addr);
90
91 addr2func(ret_addr);
92
93 printf("======\n");
94
95 rbp_value = *(uintptr_t*)(rbp_value);
96 printf("%d ## The value of RBP register is: %#lx\n", level,
rbp_value);
97 ++level;
98
99 ret_addr = *(uintptr_t*)(rbp_value + sizeof(uintptr_t));
100 addr2func(ret_addr);
101
102
103 printf("======\n");
104
105 rbp_value = *(uintptr_t*)(rbp_value);
106 printf("%d ## The value of RBP register is: %#lx\n", level,
rbp_value);
107 ++level;
108
109 ret_addr = *(uintptr_t*)(rbp_value + sizeof(uintptr_t));
110 addr2func(ret_addr);
111
112 printf("======\n");
113
114 rbp_value = *(uintptr_t*)(rbp_value);
115 printf("%d ## The value of RBP register is: %#lx\n", level,
rbp_value);
116 ++level;
117
118 ret_addr = *(uintptr_t*)(rbp_value + sizeof(uintptr_t));
119 addr2func(ret_addr);
120
121 printf("======\n");
122
123 previous_rbp = rbp_value;
124
125 rbp_value = *(uintptr_t*)(rbp_value);
126 printf("%d ## The value of RBP register is: %#lx, previous_rbp:
%#lx\n", level, rbp_value, previous_rbp);
127 ++level;
128
129 if (rbp_value > previous_rbp)
130 {
131 ret_addr = *(uintptr_t*)(rbp_value + sizeof(uintptr_t));
132 addr2func(ret_addr);
133 }
134 else
135 {
136 printf("top stack frame\n");
137 }
138
139 printf("======\n");
140
141
142 void* frame_address = __builtin_frame_address(0);
143 if (frame_address)
144 printf("Frame 0 address of f3: %p\n", frame_address);
145
146 void* return_address = __builtin_return_address(0);
147 if (return_address)
148 printf("Return 0 address of f3: %p\n", return_address);
149 }
150
151 #if 1
152 {
153 void* frame_address = __builtin_frame_address(1);
154 if (frame_address)
155 printf("Frame 1 address of f3: %p\n", frame_address);
156
157 void* return_address = __builtin_return_address(1);
158 if (return_address)
159 printf("Return 1 address of f3: %p\n", return_address);
160 }
161
162 {
163 void* frame_address = __builtin_frame_address(2);
164 if (frame_address)
165 printf("Frame 2 address of f3: %p\n", frame_address);
166
167 void* return_address = __builtin_return_address(2);
168 if (return_address)
169 printf("Return 2 address of f3: %p\n", return_address);
170 }
171 #endif
172 #if 0
173 {
174 void* frame_address = __builtin_frame_address(3);
175 if (frame_address)
176 printf("Frame 3 address of f2: %p\n", frame_address);
177
178 void* return_address = __builtin_return_address(3);
179 if (return_address)
180 printf("Return 3 address of f2: %p\n", return_address);
181 }
182 #endif
183
184 }
185 #endif
186
187 #if 0
188 printf("in f2\n");
189 f3();
190 #endif
191 }
192
193 void f2()
194 {
195 printf("in f2\n");
196 f3();
197 }
198
199 void f1()
200 {
201 printf("in f1\n");
202 f2();
203 }
204
205 int main(int argc, char *argv[])
206 {
207 f1();
208 printf("main: %p\n", main);
209 printf("f1: %p\n", f1);
210 printf("f2: %p\n", f2);
211 #if 0
212 printf("f3: %p\n", f3);
213 printf("f5: %p\n", f5);
214 #endif
215 return 0;
216 }
先來理解 c 語言呼叫 function 時做的動作, 參考 list 3 的反組譯。
list 3. objdump -d t2
1
2 t2: file format elf64-x86-64
3
611 0000000000400c50 <f5>:
612 400c50: 55 push %rbp
613 400c51: 48 89 e5 mov %rsp,%rbp
614 400c54: 48 8d 3d 49 2c 09 00 lea
0x92c49(%rip),%rdi # 4938a4 <_IO_stdin_used+0x44>
615 400c5b: e8 f0 06 01 00 callq 411350 <_IO_puts>
616 400c60: b8 00 00 00 00 mov $0x0,%eax
617 400c65: e8 e3 fe ff ff callq 400b4d
<print_backtrace>
618 400c6a: 90 nop
619 400c6b: 5d pop %rbp
620 400c6c: c3 retq
621
682
683 0000000000400d4e <f3>:
684 400d4e: 55 push %rbp
685 400d4f: 48 89 e5 mov %rsp,%rbp
686 400d52: 48 83 ec 60 sub $0x60,%rsp
687 400d56: b8 00 00 00 00 mov $0x0,%eax
688 400d5b: e8 ed fd ff ff callq 400b4d
<print_backtrace>
689 400d60: c7 45 ac 00 00 00 00 movl $0x0,-0x54(%rbp)
690 400d67: 48 8d 3d 88 2b 09 00 lea
0x92b88(%rip),%rdi # 4938f6 <_IO_stdin_used+0x96>
691 400d6e: e8 dd 05 01 00 callq 411350 <_IO_puts>
692 400d73: 48 8d 05 00 00 00 00 lea 0x0(%rip),%rax
# 400d7a <f3+0x2c>
693 400d7a: 48 89 45 b0 mov %rax,-0x50(%rbp)
694 400d7e: 48 8b 45 b0 mov -0x50(%rbp),%rax
695 400d82: 48 89 c6 mov %rax,%rsi
696 400d85: 48 8d 3d 70 2b 09 00 lea
0x92b70(%rip),%rdi # 4938fc <_IO_stdin_used+0x9c>
697 400d8c: b8 00 00 00 00 mov $0x0,%eax
698 400d91: e8 fa f3 00 00 callq 410190 <_IO_printf>
699 400d96: 48 8b 45 b0 mov -0x50(%rbp),%rax
700 400d9a: 48 89 c7 mov %rax,%rdi
701 400d9d: e8 0d ff ff ff callq 400caf <addr2func>
702 400da2: 48 8d 3d 6b 2b 09 00 lea
0x92b6b(%rip),%rdi # 493914 <_IO_stdin_used+0xb4>
703 400da9: e8 a2 05 01 00 callq 411350 <_IO_puts>
704 400dae: 48 89 e8 mov %rbp,%rax
705 400db1: 48 89 45 b8 mov %rax,-0x48(%rbp)
706 400db5: 48 8b 55 b8 mov -0x48(%rbp),%rdx
707 400db9: 8b 45 ac mov -0x54(%rbp),%eax
708 400dbc: 89 c6 mov %eax,%esi
709 400dbe: 48 8d 3d 5b 2b 09 00 lea
0x92b5b(%rip),%rdi # 493920 <_IO_stdin_used+0xc0>
710 400dc5: b8 00 00 00 00 mov $0x0,%eax
711 400dca: e8 c1 f3 00 00 callq 410190 <_IO_printf>
712 400dcf: 83 45 ac 01 addl $0x1,-0x54(%rbp)
713 400dd3: 48 8b 45 b8 mov -0x48(%rbp),%rax
714 400dd7: 48 83 c0 08 add $0x8,%rax
715 400ddb: 48 8b 00 mov (%rax),%rax
716 400dde: 48 89 45 c0 mov %rax,-0x40(%rbp)
717 400de2: 48 8b 45 c0 mov -0x40(%rbp),%rax
718 400de6: 48 89 c6 mov %rax,%rsi
719 400de9: 48 8d 3d 5a 2b 09 00 lea
0x92b5a(%rip),%rdi # 49394a <_IO_stdin_used+0xea>
720 400df0: b8 00 00 00 00 mov $0x0,%eax
721 400df5: e8 96 f3 00 00 callq 410190 <_IO_printf>
722 400dfa: 48 8b 45 c0 mov -0x40(%rbp),%rax
723 400dfe: 48 89 c7 mov %rax,%rdi
724 400e01: e8 a9 fe ff ff callq 400caf <addr2func>
725 400e06: 48 8d 3d 07 2b 09 00 lea
0x92b07(%rip),%rdi # 493914 <_IO_stdin_used+0xb4>
726 400e0d: e8 3e 05 01 00 callq 411350 <_IO_puts>
727 400e12: 48 8b 45 b8 mov -0x48(%rbp),%rax
728 400e16: 48 8b 00 mov (%rax),%rax
729 400e19: 48 89 45 b8 mov %rax,-0x48(%rbp)
730 400e1d: 48 8b 55 b8 mov -0x48(%rbp),%rdx
731 400e21: 8b 45 ac mov -0x54(%rbp),%eax
732 400e24: 89 c6 mov %eax,%esi
733 400e26: 48 8d 3d f3 2a 09 00 lea
0x92af3(%rip),%rdi # 493920 <_IO_stdin_used+0xc0>
734 400e2d: b8 00 00 00 00 mov $0x0,%eax
735 400e32: e8 59 f3 00 00 callq 410190 <_IO_printf>
736 400e37: 83 45 ac 01 addl $0x1,-0x54(%rbp)
737 400e3b: 48 8b 45 b8 mov -0x48(%rbp),%rax
738 400e3f: 48 83 c0 08 add $0x8,%rax
739 400e43: 48 8b 00 mov (%rax),%rax
740 400e46: 48 89 45 c0 mov %rax,-0x40(%rbp)
741 400e4a: 48 8b 45 c0 mov -0x40(%rbp),%rax
742 400e4e: 48 89 c7 mov %rax,%rdi
743 400e51: e8 59 fe ff ff callq 400caf <addr2func>
744 400e56: 48 8d 3d b7 2a 09 00 lea
0x92ab7(%rip),%rdi # 493914 <_IO_stdin_used+0xb4>
745 400e5d: e8 ee 04 01 00 callq 411350 <_IO_puts>
746 400e62: 48 8b 45 b8 mov -0x48(%rbp),%rax
747 400e66: 48 8b 00 mov (%rax),%rax
748 400e69: 48 89 45 b8 mov %rax,-0x48(%rbp)
749 400e6d: 48 8b 55 b8 mov -0x48(%rbp),%rdx
750 400e71: 8b 45 ac mov -0x54(%rbp),%eax
751 400e74: 89 c6 mov %eax,%esi
752 400e76: 48 8d 3d a3 2a 09 00 lea
0x92aa3(%rip),%rdi # 493920 <_IO_stdin_used+0xc0>
753 400e7d: b8 00 00 00 00 mov $0x0,%eax
754 400e82: e8 09 f3 00 00 callq 410190 <_IO_printf>
755 400e87: 83 45 ac 01 addl $0x1,-0x54(%rbp)
756 400e8b: 48 8b 45 b8 mov -0x48(%rbp),%rax
757 400e8f: 48 83 c0 08 add $0x8,%rax
758 400e93: 48 8b 00 mov (%rax),%rax
759 400e96: 48 89 45 c0 mov %rax,-0x40(%rbp)
760 400e9a: 48 8b 45 c0 mov -0x40(%rbp),%rax
761 400e9e: 48 89 c7 mov %rax,%rdi
762 400ea1: e8 09 fe ff ff callq 400caf <addr2func>
763 400ea6: 48 8d 3d 67 2a 09 00 lea
0x92a67(%rip),%rdi # 493914 <_IO_stdin_used+0xb4>
764 400ead: e8 9e 04 01 00 callq 411350 <_IO_puts>
765 400eb2: 48 8b 45 b8 mov -0x48(%rbp),%rax
766 400eb6: 48 8b 00 mov (%rax),%rax
767 400eb9: 48 89 45 b8 mov %rax,-0x48(%rbp)
768 400ebd: 48 8b 55 b8 mov -0x48(%rbp),%rdx
769 400ec1: 8b 45 ac mov -0x54(%rbp),%eax
770 400ec4: 89 c6 mov %eax,%esi
771 400ec6: 48 8d 3d 53 2a 09 00 lea
0x92a53(%rip),%rdi # 493920 <_IO_stdin_used+0xc0>
772 400ecd: b8 00 00 00 00 mov $0x0,%eax
773 400ed2: e8 b9 f2 00 00 callq 410190 <_IO_printf>
774 400ed7: 83 45 ac 01 addl $0x1,-0x54(%rbp)
775 400edb: 48 8b 45 b8 mov -0x48(%rbp),%rax
776 400edf: 48 83 c0 08 add $0x8,%rax
777 400ee3: 48 8b 00 mov (%rax),%rax
778 400ee6: 48 89 45 c0 mov %rax,-0x40(%rbp)
779 400eea: 48 8b 45 c0 mov -0x40(%rbp),%rax
780 400eee: 48 89 c7 mov %rax,%rdi
781 400ef1: e8 b9 fd ff ff callq 400caf <addr2func>
782 400ef6: 48 8d 3d 17 2a 09 00 lea
0x92a17(%rip),%rdi # 493914 <_IO_stdin_used+0xb4>
783 400efd: e8 4e 04 01 00 callq 411350 <_IO_puts>
784 400f02: 48 8b 45 b8 mov -0x48(%rbp),%rax
785 400f06: 48 89 45 c8 mov %rax,-0x38(%rbp)
786 400f0a: 48 8b 45 b8 mov -0x48(%rbp),%rax
787 400f0e: 48 8b 00 mov (%rax),%rax
788 400f11: 48 89 45 b8 mov %rax,-0x48(%rbp)
789 400f15: 48 8b 4d c8 mov -0x38(%rbp),%rcx
790 400f19: 48 8b 55 b8 mov -0x48(%rbp),%rdx
791 400f1d: 8b 45 ac mov -0x54(%rbp),%eax
792 400f20: 89 c6 mov %eax,%esi
793 400f22: 48 8d 3d 37 2a 09 00 lea
0x92a37(%rip),%rdi # 493960 <_IO_stdin_used+0x100>
794 400f29: b8 00 00 00 00 mov $0x0,%eax
795 400f2e: e8 5d f2 00 00 callq 410190 <_IO_printf>
796 400f33: 83 45 ac 01 addl $0x1,-0x54(%rbp)
797 400f37: 48 8b 45 b8 mov -0x48(%rbp),%rax
798 400f3b: 48 3b 45 c8 cmp -0x38(%rbp),%rax
799 400f3f: 76 1d jbe 400f5e <f3+0x210>
800 400f41: 48 8b 45 b8 mov -0x48(%rbp),%rax
801 400f45: 48 83 c0 08 add $0x8,%rax
802 400f49: 48 8b 00 mov (%rax),%rax
803 400f4c: 48 89 45 c0 mov %rax,-0x40(%rbp)
804 400f50: 48 8b 45 c0 mov -0x40(%rbp),%rax
805 400f54: 48 89 c7 mov %rax,%rdi
806 400f57: e8 53 fd ff ff callq 400caf <addr2func>
807 400f5c: eb 0c jmp 400f6a <f3+0x21c>
808 400f5e: 48 8d 3d 39 2a 09 00 lea
0x92a39(%rip),%rdi # 49399e <_IO_stdin_used+0x13e>
809 400f65: e8 e6 03 01 00 callq 411350 <_IO_puts>
810 400f6a: 48 8d 3d a3 29 09 00 lea
0x929a3(%rip),%rdi # 493914 <_IO_stdin_used+0xb4>
811 400f71: e8 da 03 01 00 callq 411350 <_IO_puts>
812 400f76: 48 89 6d d0 mov %rbp,-0x30(%rbp)
813 400f7a: 48 83 7d d0 00 cmpq $0x0,-0x30(%rbp)
814 400f7f: 74 18 je 400f99 <f3+0x24b>
815 400f81: 48 8b 45 d0 mov -0x30(%rbp),%rax
816 400f85: 48 89 c6 mov %rax,%rsi
817 400f88: 48 8d 3d 1f 2a 09 00 lea
0x92a1f(%rip),%rdi # 4939ae <_IO_stdin_used+0x14e>
818 400f8f: b8 00 00 00 00 mov $0x0,%eax
819 400f94: e8 f7 f1 00 00 callq 410190 <_IO_printf>
820 400f99: 48 8b 45 08 mov 0x8(%rbp),%rax
821 400f9d: 48 89 45 d8 mov %rax,-0x28(%rbp)
822 400fa1: 48 83 7d d8 00 cmpq $0x0,-0x28(%rbp)
823 400fa6: 74 18 je 400fc0 <f3+0x272>
824 400fa8: 48 8b 45 d8 mov -0x28(%rbp),%rax
825 400fac: 48 89 c6 mov %rax,%rsi
826 400faf: 48 8d 3d 13 2a 09 00 lea
0x92a13(%rip),%rdi # 4939c9 <_IO_stdin_used+0x169>
827 400fb6: b8 00 00 00 00 mov $0x0,%eax
828 400fbb: e8 d0 f1 00 00 callq 410190 <_IO_printf>
829 400fc0: 48 8b 45 00 mov 0x0(%rbp),%rax
830 400fc4: 48 89 45 e0 mov %rax,-0x20(%rbp)
831 400fc8: 48 83 7d e0 00 cmpq $0x0,-0x20(%rbp)
832 400fcd: 74 18 je 400fe7 <f3+0x299>
833 400fcf: 48 8b 45 e0 mov -0x20(%rbp),%rax
834 400fd3: 48 89 c6 mov %rax,%rsi
835 400fd6: 48 8d 3d 08 2a 09 00 lea
0x92a08(%rip),%rdi # 4939e5 <_IO_stdin_used+0x185>
836 400fdd: b8 00 00 00 00 mov $0x0,%eax
837 400fe2: e8 a9 f1 00 00 callq 410190 <_IO_printf>
838 400fe7: 48 8b 45 00 mov 0x0(%rbp),%rax
839 400feb: 48 8b 40 08 mov 0x8(%rax),%rax
840 400fef: 48 89 45 e8 mov %rax,-0x18(%rbp)
841 400ff3: 48 83 7d e8 00 cmpq $0x0,-0x18(%rbp)
842 400ff8: 74 18 je 401012 <f3+0x2c4>
843 400ffa: 48 8b 45 e8 mov -0x18(%rbp),%rax
844 400ffe: 48 89 c6 mov %rax,%rsi
845 401001: 48 8d 3d f8 29 09 00 lea
0x929f8(%rip),%rdi # 493a00 <_IO_stdin_used+0x1a0>
846 401008: b8 00 00 00 00 mov $0x0,%eax
847 40100d: e8 7e f1 00 00 callq 410190 <_IO_printf>
848 401012: 48 8b 45 00 mov 0x0(%rbp),%rax
849 401016: 48 8b 00 mov (%rax),%rax
850 401019: 48 89 45 f0 mov %rax,-0x10(%rbp)
851 40101d: 48 83 7d f0 00 cmpq $0x0,-0x10(%rbp)
852 401022: 74 18 je 40103c <f3+0x2ee>
853 401024: 48 8b 45 f0 mov -0x10(%rbp),%rax
854 401028: 48 89 c6 mov %rax,%rsi
855 40102b: 48 8d 3d ea 29 09 00 lea
0x929ea(%rip),%rdi # 493a1c <_IO_stdin_used+0x1bc>
856 401032: b8 00 00 00 00 mov $0x0,%eax
857 401037: e8 54 f1 00 00 callq 410190 <_IO_printf>
858 40103c: 48 8b 45 00 mov 0x0(%rbp),%rax
859 401040: 48 8b 00 mov (%rax),%rax
860 401043: 48 8b 40 08 mov 0x8(%rax),%rax
861 401047: 48 89 45 f8 mov %rax,-0x8(%rbp)
862 40104b: 48 83 7d f8 00 cmpq $0x0,-0x8(%rbp)
863 401050: 74 18 je 40106a <f3+0x31c>
864 401052: 48 8b 45 f8 mov -0x8(%rbp),%rax
865 401056: 48 89 c6 mov %rax,%rsi
866 401059: 48 8d 3d d7 29 09 00 lea
0x929d7(%rip),%rdi # 493a37 <_IO_stdin_used+0x1d7>
867 401060: b8 00 00 00 00 mov $0x0,%eax
868 401065: e8 26 f1 00 00 callq 410190 <_IO_printf>
869 40106a: 90 nop
870 40106b: c9 leaveq
871 40106c: c3 retq
872
873 000000000040106d <f2>:
874 40106d: 55 push %rbp
875 40106e: 48 89 e5 mov %rsp,%rbp
876 401071: 48 8d 3d db 29 09 00 lea
0x929db(%rip),%rdi # 493a53 <_IO_stdin_used+0x1f3>
877 401078: e8 d3 02 01 00 callq 411350 <_IO_puts>
878 40107d: b8 00 00 00 00 mov $0x0,%eax
879 401082: e8 c7 fc ff ff callq 400d4e <f3>
880 401087: 90 nop
881 401088: 5d pop %rbp
882 401089: c3 retq
883
884 000000000040108a <f1>:
885 40108a: 55 push %rbp
886 40108b: 48 89 e5 mov %rsp,%rbp
887 40108e: 48 8d 3d c4 29 09 00 lea
0x929c4(%rip),%rdi # 493a59 <_IO_stdin_used+0x1f9>
888 401095: e8 b6 02 01 00 callq 411350 <_IO_puts>
889 40109a: b8 00 00 00 00 mov $0x0,%eax
890 40109f: e8 c9 ff ff ff callq 40106d <f2>
891 4010a4: 90 nop
892 4010a5: 5d pop %rbp
893 4010a6: c3 retq
894
895 00000000004010a7 <main>:
896 4010a7: 55 push %rbp
897 4010a8: 48 89 e5 mov %rsp,%rbp
898 4010ab: 48 83 ec 10 sub $0x10,%rsp
899 4010af: 89 7d fc mov %edi,-0x4(%rbp)
900 4010b2: 48 89 75 f0 mov %rsi,-0x10(%rbp)
901 4010b6: b8 00 00 00 00 mov $0x0,%eax
902 4010bb: e8 ca ff ff ff callq 40108a <f1>
903 4010c0: 48 8d 35 e0 ff ff ff lea
-0x20(%rip),%rsi # 4010a7 <main>
904 4010c7: 48 8d 3d 91 29 09 00 lea
0x92991(%rip),%rdi # 493a5f <_IO_stdin_used+0x1ff>
905 4010ce: b8 00 00 00 00 mov $0x0,%eax
906 4010d3: e8 b8 f0 00 00 callq 410190 <_IO_printf>
907 4010d8: 48 8d 35 ab ff ff ff lea
-0x55(%rip),%rsi # 40108a <f1>
908 4010df: 48 8d 3d 83 29 09 00 lea
0x92983(%rip),%rdi # 493a69 <_IO_stdin_used+0x209>
909 4010e6: b8 00 00 00 00 mov $0x0,%eax
910 4010eb: e8 a0 f0 00 00 callq 410190 <_IO_printf>
911 4010f0: 48 8d 35 76 ff ff ff lea
-0x8a(%rip),%rsi # 40106d <f2>
912 4010f7: 48 8d 3d 73 29 09 00 lea
0x92973(%rip),%rdi # 493a71 <_IO_stdin_used+0x211>
913 4010fe: b8 00 00 00 00 mov $0x0,%eax
914 401103: e8 88 f0 00 00 callq 410190 <_IO_printf>
915 401108: b8 00 00 00 00 mov $0x0,%eax
916 40110d: c9 leaveq
917 40110e: c3 retq
L902 main call f1, L884 ~ L886 是進入 f1 時做的事情。
902 4010bb: e8 ca ff ff ff callq 40108a <f1>
main call f1 時, f1 會做
884 000000000040108a <f1>:
885 40108a: 55 push %rbp
886 40108b: 48 89 e5 mov %rsp,%rbp
890 40109f: e8 c9 ff ff ff callq 40106d <f2>
f1 call f2 時, f2 會做
873 000000000040106d <f2>:
874 40106d: 55 push %rbp
875 40106e: 48 89 e5 mov %rsp,%rbp
push %rbp
mov %rsp,%rbp
都是在 function 的最開始時會做的事情 (table 1. L2, L4)。table 1. main call f1,
f1 call f2 把 rsp 的內容整理起來。
table 1. main call f1; f1 call f2 stack 內容
https://photos.app.goo.gl/FCQy7GtyYKuFnHXd6
指令 call 會發生的 sp 操作: rsp - 8, 再把 ret address 放入 rsp, 我用 gdb 把從
main 到 f2 時的 stack 內容記錄在 table 1。
list 5. t2 的編譯指令
gcc -static -Wall -save-temps -g -no-pie t2.c -o t2
用了 -no-pie 是希望不要編譯成 relocation 的執行檔, 用 objdump 在對照位址時比較
方便。其他編譯選項沒太大影響。
原理是這樣, 先抓到目前 rbp 的值, 假如目前在 f2, 抓到 f2_rpb 就可以知道 f1
function 的 rpb f1_rpb, 知道了 f1_rpb 就可以知道 main function 的 rpb,
那麼知道每個 function 的 rbp 要幹麻呢? 為了取得 return address,
怎麼取得, 每層 function的 rbp + 8 的位址就可以得到 (對照 table 1. L1, L3)。
寫成 c code 就是: 上一層 function 的 return address = *(uintptr_t*)(rpb + 8)
那麼又怎麼從目前 function 的 rpb 值得到上一層的 rpb 值呢? 從 rpb 值的位址取得
(參考 table 1. L2, L4),
寫成 c code 就是: 上一層 function 的 rpb = *(uintptr_t*)(rpb)
這樣一層一層追, 就可以追到 __libc_start_main, 那 _start 追得到嗎? 抱歉,
目前我還不知道怎麼從 __libc_start_main 追到
_start (backtrace(), backtrace_symbols() 可以追到 _start)。
另外還有一個問題, 從這層的 rpb 一直追到上一層的 rpb, 要怎麼判
定追到 __libc_start_main 這層了, 這邊是觀察出來的,
上一層的 rpb 應該會比目前這層的 rpb 大, 我就這樣判定, 有可能會出錯嗎?
當然有可能, 但我想不到別的辦法了。
list 6 L105, L106 有類似的檢查條件。
list 2. L81 ~ L139 就是在做這樣的事情。另外還需要知道目前在那個 function, 所以
用了 L77 得到目前的位址。這些抓暫存器、抓目前位址都是透過 chatgpt 問到, 相當方
便。
再來有了位址要怎麼找出對應的 function, 這邊我偷懶了, 直接使用 addr2line 這個指
令幫忙。光是 addr2line 怎麼辦到的, 可能又是一個主題了。
list 2 L142, L146 就是在取得 rbp 和 return address, __builtin_frame_address(),
__builtin_return_address() 是 gcc 內建 function, 比較有可攜性。
我不滿足這樣的作法, 想「知道」怎麼辦到的, 才有了本篇文章。
在 glibc 2.39 sysdeps/i386/backtrace.c __backtrace (void **array, int size)
可以看到類似的作法。
ref:
誰在呼叫我?不同的backtrace實作說明好文章
https://reborn2266.blogspot.com/2011/12/backtrace.html
blog 版本:
https://descent-incoming.blogspot.com/2024/03/c-backtrace.html?m=0
作者: johnjohnlin (嗯?)   2024-04-28 12:12:00
只是要用的話好像有個libunwind
作者: bala045 (so sad)   2024-04-29 13:25:00
酷~
作者: DaOppaiLoli (大歐派蘿莉)   2024-05-01 07:10:00
作者: suspect1   2024-05-25 12:35:00
推推
作者: rnoro (隼)   2024-05-28 23:53:00
硬核推
作者: rodion (r-kan/reminder)   2024-05-30 18:19:00
從位址找function 可透過dladdr & nm
作者: justty32   2024-06-02 02:58:00

Links booklink

Contact Us: admin [ a t ] ucptt.com