libbfdの使い方のめもです。利用してるバージョンはbinutils-devel-2.31.1-13.fc29.x86_64です。
nmもどき
アドレスとセクション名、それにdebug情報があればファイル名と行数を表示。連想配列が使いたかったのでヘッダファイルだけで実装されてるuthashというライブラリを使ってます。
simple_nm.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <bfd.h>
#include "uthash.h"
struct section_info {
char name[64];
unsigned long vma;
UT_hash_handle hh;
};
static struct section_info *sections_info = NULL;
static void delete_all_hash_values(void)
{
struct section_info *pos, *tmp;
HASH_ITER(hh, sections_info, pos, tmp) {
HASH_DEL(sections_info, pos);
free(pos);
}
HASH_CLEAR(hh, sections_info);
}
static struct section_info *add_section(bfd *abfd, const char *sname)
{
struct section_info *info;
asection *section = bfd_get_section_by_name(abfd, sname);
if (!section)
return NULL;
info = malloc(sizeof(*info));
if (!info) {
fprintf(stderr, "malloc failed\n");
exit(-1);
}
memset(info, 0x0, sizeof(*info));
strcpy(info->name, sname);
info->vma = section->vma;
HASH_ADD_STR(sections_info, name, info);
return info;
}
static struct section_info *get_section_info(bfd *abfd, const char *sname)
{
struct section_info *info = NULL;
HASH_FIND_STR(sections_info, sname, info);
if (info)
return info;
return add_section(abfd, sname);
}
static void show_function_info(bfd *abfd, asection *debug_info, asymbol **symbols, int idx)
{
const char *file, *func;
unsigned int line;
const char *sname = symbols[idx]->section->name;
const char *name = symbols[idx]->name;
struct section_info *info = get_section_info(abfd, sname);
if (!info)
return ;
unsigned long addr = symbols[idx]->value + info->vma;
if (bfd_find_nearest_line(abfd, debug_info, symbols, addr, &file, &func, &line))
printf("0x%lx %s %s %s:%u\n", addr, sname, name, file, line);
else
printf("0x%lx %s %s\n", addr, sname, name);
}
static void show(char *prog)
{
bfd *abfd;
asection *debug_info;
size_t nsyms;
asymbol **symbols;
int ret;
bfd_init();
abfd = bfd_openr(prog, NULL);
if (!abfd) {
fprintf(stderr, "failed to open %s\n", prog);
exit(-1);
}
ret = bfd_check_format(abfd, bfd_object);
if (!ret) {
fprintf(stderr, "invalid object format\n");
exit(-1);
}
debug_info = bfd_get_section_by_name(abfd, ".debug_info");
symbols = malloc(bfd_get_symtab_upper_bound(abfd));
if (!symbols) {
fprintf(stderr, "malloc failed\n");
exit(-1);
}
nsyms = bfd_canonicalize_symtab(abfd, symbols);
for (int i = 0; i < nsyms; i++)
show_function_info(abfd, debug_info, symbols, i);
delete_all_hash_values();
free(symbols);
bfd_close(abfd);
}
int main(int argc, char **argv)
{
if (argc != 2) {
fprintf(stderr, "usage: %s <file>\n", argv[0]);
exit(-1);
}
show(argv[1]);
return 0;
}
objs=simple_nm.o
prog=simple_nm
all: $(objs)
$(CC) -O0 -g $(objs) -o $(prog) -lbfd
.c.o:
$(CC) -O0 -I. -g -c -Wall $<
test:
./$(prog) $(prog)
clean:
rm -f core* $(objs) $(prog)
実行例
0xffffffff829e0018 .bss panic_param
0xffffffff827347e3 .init.text unknown_bootoption /home/masami/linux-kernel/init/main.c:294
0xffffffff81002010 .text trace_initcall_finish_cb /home/masami/linux-kernel/init/main.c:841
0xffffffff810028e3 .text trace_initcall_start_cb /home/masami/linux-kernel/init/main.c:832
0xffffffff81002911 .text run_init_process /home/masami/linux-kernel/init/main.c:1011
0xffffffff8100294f .text try_to_run_init_process /home/masami/linux-kernel/init/main.c:1020
0xffffffff82734977 .init.text trace_event_define_fields_initcall_level /home/masami/linux-kernel/./include/trace/events/initcall.h:10
0xffffffff8273499f .init.text trace_event_define_fields_initcall_start /home/masami/linux-kernel/./include/trace/events/initcall.h:27
0xffffffff827349c4 .init.text trace_event_define_fields_initcall_finish /home/masami/linux-kernel/./include/trace/events/initcall.h:48
0xffffffff81002050 .text perf_trace_initcall_start /home/masami/linux-kernel/./include/trace/events/initcall.h:27
0xffffffff81002120 .text perf_trace_initcall_finish /home/masami/linux-kernel/./include/trace/events/initcall.h:48
0xffffffff81002200 .text trace_raw_output_initcall_level /home/masami/linux-kernel/./include/trace/events/initcall.h:10
0xffffffff81002250 .text trace_raw_output_initcall_start /home/masami/linux-kernel/./include/trace/events/initcall.h:27
0xffffffff81002290 .text trace_raw_output_initcall_finish /home/masami/linux-kernel/./include/trace/events/initcall.h:48
0xffffffff810022e0 .text __bpf_trace_initcall_level /home/masami/linux-kernel/./include/trace/events/initcall.h:10
0xffffffff810022f0 .text __bpf_trace_initcall_start /home/masami/linux-kernel/./include/trace/events/initcall.h:27
0xffffffff81002300 .text __bpf_trace_initcall_finish /home/masami/linux-kernel/./include/trace/events/initcall.h:48
0xffffffff82734a1c .init.text loglevel /home/masami/linux-kernel/init/main.c:230
0xffffffff81002310 .text initcall_blacklisted /home/masami/linux-kernel/init/main.c:790
0xffffffff82213020 .data blacklisted_initcalls
0xffffffff82329a50 .data descriptor.60757
0xffffffff82734a77 .init.text set_debug_rodata /home/masami/linux-kernel/ini
ptrace(2)で呼び出す関数を動的に変更
bfdで関数のアドレスを調べてptrace(2)でブレークポイントを設定して、ブレークポイントにきたらripを呼び出したい関数に変えて本来の関数は実行しない形。inline化されてるものには対応してませんけども。。
simple_break_point.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ptrace.h>
#include <sys/wait.h>
#include <sys/user.h>
#include <assert.h>
#include <bfd.h>
static void stub_function(void)
{
printf("stub function is called\n");
}
static void say_hello(void)
{
printf("hello, world\n");
}
void *get_function_address(char *prog_name, char *func_name)
{
bfd *abfd;
asection *text;
size_t nsyms;
asymbol **symbols;
void *addr = NULL;
int ret;
bfd_init();
abfd = bfd_openr(prog_name, NULL);
assert(abfd != NULL);
ret = bfd_check_format(abfd, bfd_object);
assert(ret != 0);
text = bfd_get_section_by_name(abfd, ".text");
symbols = malloc(bfd_get_symtab_upper_bound(abfd));
assert(symbols != NULL);
nsyms = bfd_canonicalize_symtab(abfd, symbols);
for (int i = 0; i < nsyms; i++) {
if (!strcmp(symbols[i]->name, func_name)) {
addr = (void *) (symbols[i]->value + text->vma);
break;
}
}
free(symbols);
bfd_close(abfd);
return addr;
}
int main(int argc, char **argv)
{
pid_t pid;
long ret;
void *addr;
if (argc != 2) {
printf("usage: %s <function name>\n", argv[0]);
exit(-1);
}
addr = get_function_address(argv[0], argv[1]);
if (!addr) {
printf("function %s is not found\n", argv[1]);
exit(-1);
}
pid = fork();
assert(pid >= 0);
if (!pid) {
say_hello();
} else {
int status;
void *break_point;
long orig_text;
ret = ptrace(PTRACE_ATTACH, pid, NULL, NULL);
assert(ret != -1);
waitpid(pid, &status, 0);
orig_text = ptrace(PTRACE_PEEKTEXT, pid, addr, NULL);
break_point = (void *) (unsigned long) (orig_text | 0xcc);
ret = ptrace(PTRACE_POKETEXT, pid, addr, break_point);
assert(pid != -1);
printf("[*] set break point at %p\n", addr);
ret = ptrace(PTRACE_CONT, pid, NULL, NULL);
assert(pid != -1);
waitpid(pid, &status, 0);
if (WIFEXITED(status)) {
printf("program normally finishd\n");
exit(0);
} else if (WIFSTOPPED(status)) {
struct user_regs_struct regs = { 0 };
ret = ptrace(PTRACE_GETREGS, pid, 0, ®s);
assert(ret != -1);
regs.rip = (unsigned long long) stub_function;
ret = ptrace(PTRACE_SETREGS, pid, 0, ®s);
assert(ret != -1);
ret = ptrace(PTRACE_CONT, pid, NULL, NULL);
assert(pid != -1);
} else {
printf("unkown error\n");
exit(-1);
}
}
return 0;
}
objs=simple_break_point.o
prog=simple_break_point
all: $(objs)
$(CC) -O0 -g $(objs) -o $(prog) -lbfd
.c.o:
$(CC) -O0 -g -c -Wall $<
test:
./$(prog) say_hello
clean:
rm -f core* $(objs) $(prog)
実行例
ここでブレークポイントを設定する関数のアドレスはnmで見るとこうなってます。
masami@saga:~/codes/simple_break_point$ nm ./simple_break_point | grep "say_hello\|main"
U __libc_start_main@@GLIBC_2.2.5
0000000000402a69 T main
00000000004028c7 t say_hello
masami@saga:~/codes/simple_break_point$ ./simple_break_point say_hello
[*] set break point at 0x4028c7
stub function is called
すでに実行されたmain()に設定することでブレークポイントにヒットしない感じで。
mi@saga:~/codes/simple_break_point$ ./simple_break_point main
[*] set break point at 0x402a69
hello, world
program normally finishd