비디오 메모리 주소에 중단점을 설정하는 것은 좋아 보이지만 가장 사용자 친화적인 도구를 제공하지는 않습니다. 또한 코드와 동일한 수준의 구체성에서 디버깅할 수 있도록 소스 코드 줄과 함수 항목 주소에 중단점을 설정할 수 있기를 바랍니다.
이 문서에서는 디버거에 소스 수준 중단점을 추가합니다. 우리가 이미 지원하는 모든 기능을 사용하면 처음에 들리는 것보다 훨씬 쉽습니다. 또한 코드나 데이터를 찾고 연결 개념을 이해하는 데 유용한 기호의 유형과 주소를 가져오는 명령을 추가할 것입니다.
시리즈 색인
이전 글이 공개됨에 따라 이 링크는 점차적으로 적용됩니다.
소스 코드 수준 중단점 호출 스택을 실행하고 다음 단계의 변수를 읽을 수 있도록 환경 중단점 레지스터 및 비디오 메모리 엘프와 드워프 소스 코드 및 신호 소스 코드 수준을 준비합니다
중단점
난쟁이
Elves and dwarves 이 문서에서는 DWARF 디버깅 정보가 작동하는 방식과 이를 사용하여 기계어 코드를 상위 수준 소스 코드에 매핑하는 방법을 설명합니다. DWARF에는 표현 계층 간에 코드 위치를 변환할 수 있는 함수의 주소 범위와 라인 테이블이 포함되어 있다는 점을 기억하세요. 이 기능을 사용하여 중단점을 구현하겠습니다.
함수 입력
오버로딩, 멤버 함수 등 Linux 운영 체제 원칙을 고려하면 함수 이름에 중단점을 설정하는 것이 다소 복잡할 수 있으며, 모든 컴파일 단위를 순회하여 찾고 있는 이름과 일치하는 함수를 검색합니다. DWARF 정보는 다음과 같습니다.
으아악
DW_AT_name을 일치시키고 DW_AT_low_pc(함수의 시작 주소)를 사용하여 중단점을 설정하려고 합니다.
으아악
이 코드에서 조금 이상해 보이는 유일한 점은 ++ 항목입니다. 문제는 함수의 DW_AT_low_pc가 함수에 대한 사용자 코드의 시작 주소를 가리키지 않고 프롤로그의 시작을 가리킨다는 것입니다. 컴파일러는 일반적으로 스택 저장 및 복원, 스택 테이블 포인터 연산 등에 사용되는 함수의 프롤로그와 에필로그를 출력합니다. 이것은 우리에게 그다지 유용하지 않으므로 프롤로그 대신 사용자 코드의 첫 번째 줄을 얻기 위해 항목 줄을 하나씩 증가시킵니다. DWARF 라인 테이블에는 실제로 항목을 함수 프롤로그 뒤의 첫 번째 라인으로 표시하는 기능이 있지만 모든 컴파일러가 이를 출력하는 것은 아니기 때문에 원래 방식을 사용했습니다.
소스 코드 라인
고수준 소스 코드 줄에 중단점을 설정하려면 DWARF에서 줄 번호를 주소로 변환해야 합니다. 우리는 컴파일 단위를 반복하여 이름이 주어진 파일과 일치하는 것을 찾은 다음 주어진 줄에 해당하는 항목을 찾습니다.
DWARF는 다음과 같습니다:
으아악
그래서 ab.cpp의 5번째 줄에 중단점을 설정하려면 해당 줄(0x004004e3)과 관련된 항목을 찾아서 중단점을 설정하면 됩니다.
으아악
我这儿做了is_suffixhack,这样你可以输入c.cpp代表a/b/c.cpp。其实你实际上应当使用大小写敏感路径处理库或则其它东西,而且我比较懒。entry.is_stmt是检测行表入口是否被标记为一个句子的开头,这是由编译器按照它觉得是断点的最佳目标的地址设置的。
符号查找
当我们在对象文件层时,符号是王者。函数用符号命名红旗linux系统,全局变量用符号命名,你得到一个符号,我们得到一个符号,每位人都得到一个符号。在给定的对象文件中linux vector 头文件,一些符号可能引用其他对象文件或共享库,链接器将从符号引用创建一个可执行程序。
可以在正确命名的符号表中查找符号,它储存在二补码文件的ELF部份中。辛运的是,libelfin有一个不错的插口来做这件事,所以我们不须要自己处理所有的ELF的事情。为了让你晓得我们在处理哪些,下边是一个二补码文件的.symtab部份的轮询,它由readelf生成:
Num: Value Size Type Bind Vis Ndx Name 0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND 1: 0000000000400238 0 SECTION LOCAL DEFAULT 1 2: 0000000000400254 0 SECTION LOCAL DEFAULT 2 3: 0000000000400278 0 SECTION LOCAL DEFAULT 3 4: 00000000004002c8 0 SECTION LOCAL DEFAULT 4 5: 0000000000400430 0 SECTION LOCAL DEFAULT 5 6: 00000000004004e4 0 SECTION LOCAL DEFAULT 6 7: 0000000000400508 0 SECTION LOCAL DEFAULT 7 8: 0000000000400528 0 SECTION LOCAL DEFAULT 8 9: 0000000000400558 0 SECTION LOCAL DEFAULT 9 10: 0000000000400570 0 SECTION LOCAL DEFAULT 10 11: 0000000000400714 0 SECTION LOCAL DEFAULT 11 12: 0000000000400720 0 SECTION LOCAL DEFAULT 12 13: 0000000000400724 0 SECTION LOCAL DEFAULT 13 14: 0000000000400750 0 SECTION LOCAL DEFAULT 14 15: 0000000000600e18 0 SECTION LOCAL DEFAULT 15 16: 0000000000600e20 0 SECTION LOCAL DEFAULT 16 17: 0000000000600e28 0 SECTION LOCAL DEFAULT 17 18: 0000000000600e30 0 SECTION LOCAL DEFAULT 18 19: 0000000000600ff0 0 SECTION LOCAL DEFAULT 19 20: 0000000000601000 0 SECTION LOCAL DEFAULT 20 21: 0000000000601018 0 SECTION LOCAL DEFAULT 21 22: 0000000000601028 0 SECTION LOCAL DEFAULT 22 23: 0000000000000000 0 SECTION LOCAL DEFAULT 23 24: 0000000000000000 0 SECTION LOCAL DEFAULT 24 25: 0000000000000000 0 SECTION LOCAL DEFAULT 25 26: 0000000000000000 0 SECTION LOCAL DEFAULT 26 27: 0000000000000000 0 SECTION LOCAL DEFAULT 27 28: 0000000000000000 0 SECTION LOCAL DEFAULT 28 29: 0000000000000000 0 SECTION LOCAL DEFAULT 29 30: 0000000000000000 0 SECTION LOCAL DEFAULT 30 31: 0000000000000000 0 FILE LOCAL DEFAULT ABS init.c 32: 0000000000000000 0 FILE LOCAL DEFAULT ABS crtstuff.c 33: 0000000000600e28 0 OBJECT LOCAL DEFAULT 17 __JCR_LIST__ 34: 00000000004005a0 0 FUNC LOCAL DEFAULT 10 deregister_tm_clones 35: 00000000004005e0 0 FUNC LOCAL DEFAULT 10 register_tm_clones 36: 0000000000400620 0 FUNC LOCAL DEFAULT 10 __do_global_dtors_aux 37: 0000000000601028 1 OBJECT LOCAL DEFAULT 22 completed.6917 38: 0000000000600e20 0 OBJECT LOCAL DEFAULT 16 __do_global_dtors_aux_fin 39: 0000000000400640 0 FUNC LOCAL DEFAULT 10 frame_dummy 40: 0000000000600e18 0 OBJECT LOCAL DEFAULT 15 __frame_dummy_init_array_ 41: 0000000000000000 0 FILE LOCAL DEFAULT ABS /super/secret/path/MiniDbg/ 42: 0000000000000000 0 FILE LOCAL DEFAULT ABS crtstuff.c 43: 0000000000400818 0 OBJECT LOCAL DEFAULT 14 __FRAME_END__ 44: 0000000000600e28 0 OBJECT LOCAL DEFAULT 17 __JCR_END__ 45: 0000000000000000 0 FILE LOCAL DEFAULT ABS 46: 0000000000400724 0 NOTYPE LOCAL DEFAULT 13 __GNU_EH_FRAME_HDR 47: 0000000000601000 0 OBJECT LOCAL DEFAULT 20 _GLOBAL_OFFSET_TABLE_ 48: 0000000000601028 0 OBJECT LOCAL DEFAULT 21 __TMC_END__ 49: 0000000000601020 0 OBJECT LOCAL DEFAULT 21 __dso_handle 50: 0000000000600e20 0 NOTYPE LOCAL DEFAULT 15 __init_array_end 51: 0000000000600e18 0 NOTYPE LOCAL DEFAULT 15 __init_array_start 52: 0000000000600e30 0 OBJECT LOCAL DEFAULT 18 _DYNAMIC 53: 0000000000601018 0 NOTYPE WEAK DEFAULT 21 data_start 54: 0000000000400710 2 FUNC GLOBAL DEFAULT 10 __libc_csu_fini 55: 0000000000400570 43 FUNC GLOBAL DEFAULT 10 _start 56: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__ 57: 0000000000400714 0 FUNC GLOBAL DEFAULT 11 _fini 58: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@@GLIBC_ 59: 0000000000400720 4 OBJECT GLOBAL DEFAULT 12 _IO_stdin_used 60: 0000000000601018 0 NOTYPE GLOBAL DEFAULT 21 __data_start 61: 00000000004006a0 101 FUNC GLOBAL DEFAULT 10 __libc_csu_init 62: 0000000000601028 0 NOTYPE GLOBAL DEFAULT 22 __bss_start 63: 0000000000601030 0 NOTYPE GLOBAL DEFAULT 22 _end 64: 0000000000601028 0 NOTYPE GLOBAL DEFAULT 21 _edata 65: 0000000000400670 44 FUNC GLOBAL DEFAULT 10 main 66: 0000000000400558 0 FUNC GLOBAL DEFAULT 9 _init
你可以在对象文件中见到用于设置环境的好多符号,最后还可以见到main符号。
我们对符号的类型、名称和值(地址)感兴趣。我们有一个该类型的symbol_type枚举,并使用一个std::string作为名称,std::uintptr_t作为地址:
enum class symbol_type { notype, // No type (e.g., absolute symbol) object, // Data object func, // Function entry point section, // Symbol is associated with a section file, // Source file associated with the }; // object file std::string to_string (symbol_type st) { switch (st) { case symbol_type::notype: return "notype"; case symbol_type::object: return "object"; case symbol_type::func: return "func"; case symbol_type::section: return "section"; case symbol_type::file: return "file"; } } struct symbol { symbol_type type; std::string name; std::uintptr_t addr; };
我们须要将从libelfin获得的符号类型映射到我们的枚举,由于我们不希望依赖关系破环这个插口。辛运的是,我为所有的东西选了同样的名子,所以这样很简单:
symbol_type to_symbol_type(elf::stt sym) { switch (sym) { case elf::stt::notype: return symbol_type::notype; case elf::stt::object: return symbol_type::object; case elf::stt::func: return symbol_type::func; case elf::stt::section: return symbol_type::section; case elf::stt::file: return symbol_type::file; default: return symbol_type::notype; } };
最后我们要查找符号。为了说明的目的,我循环查找符号表的ELF部份,之后搜集我在其中找到的任意符号到std::vector中。更智能的实现可以构建从名称到符号的映射,这样你只须要查看一次数据就行了。
std::vector debugger::lookup_symbol(const std::string& name) { std::vector syms; for (auto &sec : m_elf.sections()) { if (sec.get_hdr().type != elf::sht::symtab && sec.get_hdr().type != elf::sht::dynsym) continue; for (auto sym : sec.as_symtab()) { if (sym.get_name() == name) { auto &d = sym.get_data(); syms.push_back(symbol{to_symbol_type(d.type()), sym.get_name(), d.value}); } } } return syms; }
添加命令
一如往常,我们须要添加一些更多的命令来向用户曝露功能。对于断点,我使用GDB风格的插口linux vector 头文件,其中断点类型是通过你传递的参数推论的,而不用要求显式切换:
else if(is_prefix(command, "break")) { if (args[1][0] == '0' && args[1][1] == 'x') { std::string addr {args[1], 2}; set_breakpoint_at_address(std::stol(addr, 0, 16)); } else if (args[1].find(':') != std::string::npos) { auto file_and_line = split(args[1], ':'); set_breakpoint_at_source_line(file_and_line[0], std::stoi(file_and_line[1])); } else { set_breakpoint_at_function(args[1]); } }
对于符号,我们将查找符号并复印出我们发觉的任何匹配项:
else if(is_prefix(command, "symbol")) { auto syms = lookup_symbol(args[1]); for (auto&& s : syms) { std::cout << s.name << ' ' << to_string(s.type) << " 0x" << std::hex << s.addr << std::endl; } }
测试一下
在一个简单的二补码文件上启动调试器,并设置源代码级别的断点。在一些foo函数上设置一个断点,见到我的调试器停在它前面是我这个项目最有价值的时刻之一。
符号查找可以通过在程序中添加一些函数或全局变量并查找它们的名称来进行测试。请注意,假若你正在编译C++代码,你还须要考虑名称重整。
위 내용은 단계별 실행을 위해 환경 레지스터 및 메모리 엘프와 드워프 소스 코드 및 신호 소스 코드 레벨 준비의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!