libunwind¶
Introduction¶
libunwind 是一個跨平台的 C API, 提供取得程式 call-chain 的方式, 原先由 HP 開發, 以 MIT 授權釋出。
一般會把 call stack 上加入 subroutine 進入點的動作稱為 winding
,
而反過來則稱為 unwinding
,
C 的 return 就是一個 unwinding
的例子,
但是使用 libunwind 的話可以一次 unwind 好幾個 function,
除此之外可以取得 call stack 的資訊,
藉此獲得 backtrace、原先的呼叫點。
Example¶
Build libunwind¶
$ git clone git://git.sv.gnu.org/libunwind.git
$ cd libunwind
$ ./autogen.sh
$ ./configure --prefix=/usr/local/
$ make -j8
$ make install
# test
$ make check
# bench
$ cd tests
$ make perf
Backtrace¶
backtrace.c
:
#define UNW_LOCAL_ONLY // We only need local unwinder.
// On x86_64 machine, you will need to link with libunwind-x86_64 without this define.
#include "libunwind.h"
#include <stdio.h> // printf
void show_backtrace() {
unw_cursor_t cursor;
unw_context_t uc;
unw_word_t ip, sp;
char buf[4096];
unw_word_t offset;
unw_getcontext(&uc); // store registers
unw_init_local(&cursor, &uc); // initialze with context
printf("==========\n");
while (unw_step(&cursor) > 0) { // unwind to older stack frame
unw_get_reg(&cursor, UNW_REG_IP, &ip); // read register, rip
unw_get_reg(&cursor, UNW_REG_SP, &sp); // read register, rbp
unw_get_proc_name(&cursor, buf, 4095, &offset); // get name and offset
printf("0x%016lx <%s+0x%lx>\n", ip, buf, offset); // x86_64, unw_word_t == uint64_t
}
printf("==========\n");
}
void func() {
show_backtrace();
}
int main() {
show_backtrace();
func();
show_backtrace();
return 0;
}
Build and Run :
# libunwind.h use sys/ucontext.h, which need GNU11 instead of pure C11
$ gcc -std=gnu11 -Wall -lunwind backtrace.c -o backtrace
$ ./backtrace
==========
0x0000000000400914 <main+0x14>
0x00007fa19af32790 <__libc_start_main+0xf0>
0x00000000004006f9 <_start+0x29>
0x0000000000000000 <+0x29>
==========
==========
0x00000000004008f9 <func+0x9>
0x0000000000400919 <main+0x19>
0x00007fa19af32790 <__libc_start_main+0xf0>
0x00000000004006f9 <_start+0x29>
0x0000000000000000 <+0x29>
==========
==========
0x000000000040091e <main+0x1e>
0x00007fa19af32790 <__libc_start_main+0xf0>
0x00000000004006f9 <_start+0x29>
0x0000000000000000 <+0x29>
==========
Unwind¶
unwind.c
:
#define UNW_LOCAL_ONLY // We only need local unwinder.
// On x86_64 machine, you will need to link with libunwind-x86_64 without this define.
#include "libunwind.h"
#include <stdio.h> // printf
void skip_func() {
unw_cursor_t cursor;
unw_context_t uc;
unw_getcontext(&uc);
unw_init_local(&cursor, &uc);
unw_step(&cursor);
unw_step(&cursor);
unw_resume(&cursor); // restore the machine state
printf("will be skipped\n"); // won't be executed
}
void skipped_func() {
skip_func();
printf("will be skipped\n"); // won't be executed
}
int main() {
printf("start\n");
skipped_func();
printf("end\n");
return 0;
}
Build and Run :
$ gcc -std=gnu11 -Wall -lunwind unwind.c
$ ./unwind
start
end
Use getcontext/setcontext to unwind¶
[待研究細節] source code 來自《Binary Hacks》,
目前嘗試 x86 版 (-m32
) 用 gcc 邊完後可以正常執行完,
clang 編的話則是結束後會 coredump。x86_64 版本則是連用 gcc 編都會在結束時 coredump。
需要再研究細節,還有結構需要的調整。
x86¶
#define _GNU_SOURCE
#include <stdio.h>
#include <ucontext.h>
typedef struct layout {
struct layout *ebp;
void *ret;
} layout;
void skip_func() {
ucontext_t uc;
layout *ebp = __builtin_frame_address(0);
ebp = ebp->ebp;
getcontext(&uc);
uc.uc_mcontext.gregs[REG_EIP] = (unsigned int)ebp->ret;
uc.uc_mcontext.gregs[REG_EBP] = (unsigned int)ebp->ebp;
setcontext(&uc);
printf("will be skipped\n"); // won't be executed
}
void skipped_func() {
skip_func();
printf("will be skipped\n"); // won't be executed
}
int main() {
printf("start\n");
skipped_func();
printf("end\n");
return 0;
}
x86_64¶
#define _GNU_SOURCE
#include <stdio.h>
#include <ucontext.h>
typedef struct layout {
struct layout *rbp;
void *ret;
} layout;
void skip_func() {
ucontext_t uc;
layout *rbp = __builtin_frame_address(0);
rbp = rbp->rbp;
getcontext(&uc);
uc.uc_mcontext.gregs[REG_RIP] = (unsigned long)rbp->ret;
uc.uc_mcontext.gregs[REG_RBP] = (unsigned long)rbp->rbp;
setcontext(&uc);
printf("will be skipped\n"); // won't be executed
}
void skipped_func() {
skip_func();
printf("will be skipped\n"); // won't be executed
}
int main() {
printf("start\n");
skipped_func();
printf("end\n");
return 0;
}
libunwind - test ptraace¶
Reference¶
- Wikipedia - setcontext
- specified in POSIX.1-2001
- POSIX.1-2004 obsoleted these functions
- in POSIX.1-2008 they were removed
《Binary Hacks》 - #73
Linaro - ARM32/64: perf: dwarf stack frame unwinding support