Rumah > Artikel > Tutorial sistem > Pengembangan tindanan debugger Linux!
Pengenalan | Kadangkala maklumat paling penting yang perlu anda ketahui ialah bagaimana keadaan program semasa anda sampai ke sana. Terdapat arahan jejak belakang, yang memberikan anda rangkaian panggilan fungsi semasa program anda. Siaran ini akan menunjukkan kepada anda cara melaksanakan penyalahan tindanan pada x86_64 untuk menjana jejak balik sedemikian. |
Pautan ini akan disiarkan secara langsung apabila siaran lain diterbitkan.
Gunakan program berikut sebagai contoh:
void a() { //stopped here } void b() { a(); } void c() { a(); } int main() { b(); c(); }
Jika penyahpepijat berhenti di baris //berhenti di sini', terdapat dua cara untuk mencapainya: main->b->a atau main->c->a`. Jika kami menetapkan titik putus dengan LLDB, teruskan pelaksanaan dan minta jejak balik, maka kami mendapat perkara berikut:
* frame #0: 0x00000000004004da a.out`a() + 4 at bt.cpp:3 frame #1: 0x00000000004004e6 a.out`b() + 9 at bt.cpp:6 frame #2: 0x00000000004004fe a.out`main + 9 at bt.cpp:14 frame #3: 0x00007ffff7a2e830 libc.so.6`__libc_start_main + 240 at libc-start.c:291 frame #4: 0x0000000000400409 a.out`_start + 41
Ini bermakna kita sedang dalam fungsi a, a melompat dari fungsi b, b melompat dari utama, dsb. Dua bingkai terakhir ialah cara pengkompil membongkar fungsi utama.
Persoalannya sekarang ialah bagaimana kami melaksanakannya pada x86_64. Pendekatan yang paling mantap ialah menghuraikan bahagian .eh_frame bagi fail ELF dan memikirkan cara untuk melepaskan timbunan dari sana, tetapi itu akan menyusahkan. Anda boleh melakukannya menggunakan libunwind atau serupa, tetapi itu membosankan. Sebaliknya, kami menganggap bahawa pengkompil telah menyediakan timbunan dalam beberapa cara dan kami akan melintasinya secara manual. Untuk melakukan ini, kita perlu memahami susun atur timbunan terlebih dahulu.
High | ... | +---------+ +24| Arg 1 | +---------+ +16| Arg 2 | +---------+ + 8| Return | +---------+ EBP+--> |Saved EBP| +---------+ - 8| Var 1 | +---------+ ESP+--> | Var 2 | +---------+ | ... | Low
Seperti yang anda lihat, penuding bingkai bingkai tindanan terakhir disimpan pada permulaan bingkai tindanan semasa, mencipta senarai penuding yang dipautkan. Tindanan dibongkar berdasarkan senarai terpaut ini. Kita boleh mencari fungsi untuk bingkai seterusnya dalam senarai dengan mencari alamat pemulangan dalam mesej DWARF. Sesetengah penyusun akan mengabaikan penjejakan alamat asas bingkai EBP kerana ini boleh dinyatakan sebagai offset daripada ESP dan membebaskan daftar tambahan. Walaupun dengan pengoptimuman didayakan, menghantar -fno-omit-frame-pointer kepada GCC atau Clang akan memaksanya untuk mengikut konvensyen yang kami bergantung kepada.
Kami akan melakukan semua kerja dalam fungsi print_backtrace:
void debugger::print_backtrace() {
Perkara pertama yang perlu diputuskan ialah format yang hendak digunakan untuk mencetak maklumat bingkai. Saya menggunakan lambda untuk melancarkan kaedah ini:
auto output_frame = [frame_number = 0] (auto&& func) mutable { std::cout << "frame #" << frame_number++ << ": 0x" << dwarf::at_low_pc(func) << ' ' << dwarf::at_name(func) << std::endl; };
Bingkai pertama yang dicetak ialah bingkai yang sedang dilaksanakan. Kita boleh mendapatkan maklumat tentang bingkai ini dengan mencari kaunter program semasa dalam DWARF:
auto current_func = get_function_from_pc(get_pc()); output_frame(current_func);
Seterusnya kita perlu mendapatkan penunjuk bingkai dan alamat pemulangan fungsi semasa. Penunjuk bingkai disimpan dalam daftar rbp dan alamat pemulangan ialah 8 bait yang disusun daripada penuding bingkai.
auto frame_pointer = get_register_value(m_pid, reg::rbp); auto return_address = read_memory(frame_pointer+8);
Kini kami mempunyai semua maklumat yang kami perlukan untuk mengembangkan timbunan. Saya terus berehat sehingga penyahpepijat mencecah utama, tetapi anda juga boleh memilih untuk berhenti apabila penuding bingkai ialah 0x0, iaitu fungsi yang anda panggil sebelum memanggil fungsi utama. Kami akan mengambil penunjuk bingkai dan alamat pemulangan dari setiap bingkai dan mencetak maklumat tersebut.
while (dwarf::at_name(current_func) != "main") { current_func = get_function_from_pc(return_address); output_frame(current_func); frame_pointer = read_memory(frame_pointer); return_address = read_memory(frame_pointer+8); } }
Itu sahaja! Inilah keseluruhan fungsi:
void debugger::print_backtrace() { auto output_frame = [frame_number = 0] (auto&& func) mutable { std::cout << "frame #" << frame_number++ << ": 0x" << dwarf::at_low_pc(func) << ' ' << dwarf::at_name(func) << std::endl; }; auto current_func = get_function_from_pc(get_pc()); output_frame(current_func); auto frame_pointer = get_register_value(m_pid, reg::rbp); auto return_address = read_memory(frame_pointer+8); while (dwarf::at_name(current_func) != "main") { current_func = get_function_from_pc(return_address); output_frame(current_func); frame_pointer = read_memory(frame_pointer); return_address = read_memory(frame_pointer+8); } }Tambah arahan
Sudah tentu, kita mesti mendedahkan arahan ini kepada pengguna.
else if(is_prefix(command, "backtrace")) { print_backtrace(); }Ujian
Salah satu cara untuk menguji fungsi ini ialah dengan menulis program ujian dengan sekumpulan fungsi kecil yang memanggil satu sama lain. Tetapkan beberapa titik putus, lompat sekeliling kod dan pastikan jejak balik anda adalah tepat.
Kami telah melangkah jauh dari program yang hanya boleh melahirkan dan melekat pada program lain. Artikel terakhir dalam siri ini akan melengkapkan pelaksanaan penyahpepijat dengan menyokong pembolehubah membaca dan menulis. Sehingga itu, anda boleh mencari kod untuk siaran ini di sini.
Atas ialah kandungan terperinci Pengembangan tindanan debugger Linux!. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!