Pengenalan | Pembolehubah adalah licik. Kadang-kadang mereka akan dengan senang hati duduk dalam daftar, hanya untuk berakhir di timbunan sebaik sahaja mereka berpusing. Untuk tujuan pengoptimuman, pengkompil boleh membuangnya keluar dari tetingkap sepenuhnya. Tidak kira bagaimana pembolehubah bergerak dalam ingatan, kita memerlukan beberapa cara untuk menjejak dan memanipulasinya dalam penyahpepijat. Artikel ini akan mengajar anda cara mengendalikan pembolehubah dalam penyahpepijat dan menunjukkan pelaksanaan mudah menggunakan libelfin. |
Sebelum bermula, sila pastikan anda menggunakan versi libelfin fbreg di cawangan saya. Ini mengandungi beberapa penggodaman untuk menyokong mendapatkan alamat asas bingkai tindanan semasa dan menilai senarai lokasi, tidak satu pun daripadanya disediakan oleh libelfin asli. Anda mungkin perlu menghantar parameter -gdwarf-2 kepada GCC untuk menjana mesej DWARF yang serasi. Tetapi sebelum melaksanakannya, saya akan memperincikan cara pengekodan kedudukan berfungsi dalam spesifikasi DWARF 5 terkini. Jika anda ingin mengetahui lebih lanjut, anda boleh mendapatkan standard di sini.
Lokasi kerdilLokasi pembolehubah dalam ingatan pada masa tertentu dikodkan dalam mesej DWARF menggunakan atribut DW_AT_location. Perihalan lokasi boleh menjadi perihalan lokasi tunggal, perihalan lokasi komposit atau senarai lokasi.
DW_AT_location dikodkan dalam tiga cara berbeza bergantung pada jenis perihalan lokasi. exprloc mengekod huraian kedudukan mudah dan komposit. Ia terdiri daripada panjang bait diikuti dengan ungkapan DWARF atau perihalan lokasi. Senarai lokasi yang dikodkan untuk loclist dan loclistptr, yang menyediakan indeks atau offset dalam bahagian .debug_loclists, yang menerangkan senarai lokasi sebenar.
Ekspresi kerdilGunakan ungkapan DWARF untuk mengira kedudukan sebenar pembolehubah. Ini termasuk satu siri operasi yang memanipulasi nilai tindanan. Terdapat banyak operasi DWARF yang tersedia, jadi saya tidak akan menerangkannya secara terperinci. Sebaliknya, saya akan memberikan beberapa contoh daripada setiap ungkapan untuk memberi anda sesuatu untuk diusahakan. Juga, jangan takut tentang perkara ini; libelfin akan menangani semua kerumitan ini untuk kami.
Perwakilan jenis DWARF perlu cukup berkuasa untuk menyediakan perwakilan pembolehubah yang berguna untuk menyahpepijat pengguna. Pengguna selalunya mahu dapat menyahpepijat pada peringkat aplikasi dan bukannya pada peringkat mesin, dan mereka perlu memahami perkara yang dilakukan oleh pembolehubah mereka.
Jenis DWARF dikodkan dalam DIE bersama-sama dengan kebanyakan maklumat penyahpepijatan yang lain. Mereka boleh mempunyai sifat yang menunjukkan nama mereka, pengekodan, saiz, bait, dsb. Pelbagai teg jenis tersedia untuk mewakili penunjuk, tatasusunan, struktur, typedef dan apa-apa lagi yang mungkin anda lihat dalam program C atau C++.
Ambil struktur mudah ini sebagai contoh:
struct test{ int i; float j; int k[42]; test* next; };
Induk MATI struktur ini adalah seperti ini:
< 1><0x0000002a> DW_TAG_structure_type DW_AT_name "test" DW_AT_byte_size 0x000000b8 DW_AT_decl_file 0x00000001 test.cpp DW_AT_decl_line 0x00000001
Apa yang dinyatakan di atas ialah kami mempunyai struktur yang dipanggil ujian, dengan saiz 0xb8, diisytiharkan pada baris 1 test.cpp. Seterusnya terdapat beberapa sub-DIE yang menggambarkan ahli.
< 2><0x00000032> DW_TAG_member DW_AT_name "i" DW_AT_type <0x00000063> DW_AT_decl_file 0x00000001 test.cpp DW_AT_decl_line 0x00000002 DW_AT_data_member_location 0 < 2><0x0000003e> DW_TAG_member DW_AT_name "j" DW_AT_type <0x0000006a> DW_AT_decl_file 0x00000001 test.cpp DW_AT_decl_line 0x00000003 DW_AT_data_member_location 4 < 2><0x0000004a> DW_TAG_member DW_AT_name "k" DW_AT_type <0x00000071> DW_AT_decl_file 0x00000001 test.cpp DW_AT_decl_line 0x00000004 DW_AT_data_member_location 8 < 2><0x00000056> DW_TAG_member DW_AT_name "next" DW_AT_type <0x00000084> DW_AT_decl_file 0x00000001 test.cpp DW_AT_decl_line 0x00000005 DW_AT_data_member_location 176(as signed = -80)
Setiap ahli mempunyai nama, jenis (iaitu offset DIE), fail dan baris pengisytiharan, dan ofset bait kepada struktur di mana ahlinya berada. Titik jenisnya adalah seperti berikut.
< 1><0x00000063> DW_TAG_base_type DW_AT_name "int" DW_AT_encoding DW_ATE_signed DW_AT_byte_size 0x00000004 < 1><0x0000006a> DW_TAG_base_type DW_AT_name "float" DW_AT_encoding DW_ATE_float DW_AT_byte_size 0x00000004 < 1><0x00000071> DW_TAG_array_type DW_AT_type <0x00000063> < 2><0x00000076> DW_TAG_subrange_type DW_AT_type <0x0000007d> DW_AT_count 0x0000002a < 1><0x0000007d> DW_TAG_base_type DW_AT_name "sizetype" DW_AT_byte_size 0x00000008 DW_AT_encoding DW_ATE_unsigned < 1><0x00000084> DW_TAG_pointer_type DW_AT_type <0x0000002a>
Seperti yang anda lihat, int pada komputer riba saya ialah jenis integer bertanda 4-bait dan apungan ialah nombor titik terapung 4-bait. Jenis tatasusunan integer mempunyai elemen 2a dengan menunjuk ke taip int sebagai jenis elemen dan jenis saiz (anggap ia sebagai size_t) sebagai jenis indeks. Jenis ujian * ialah DW_TAG_pointer_type, yang merujuk kepada ujian DIE.
Melaksanakan pembaca pembolehubah mudahSeperti yang dinyatakan di atas, libelfin akan mengendalikan kebanyakan kerumitan untuk kita. Walau bagaimanapun, ia tidak melaksanakan semua kaedah untuk mewakili kedudukan berubah, dan pengendalian ini dalam kod kami akan menjadi sangat rumit. Oleh itu, saya kini memilih untuk menyokong exprloc sahaja. Sila tambah sokongan untuk lebih banyak jenis ungkapan seperti yang diperlukan. Jika anda benar-benar berani, sila serahkan tampung kepada libelfin untuk membantu melengkapkan sokongan yang diperlukan!
Mengendalikan pembolehubah terutamanya melibatkan pengesanan bahagian yang berbeza dalam ingatan atau daftar, dan membaca atau menulis adalah sama seperti sebelumnya. Untuk memastikan perkara mudah, saya hanya akan memberitahu anda cara melaksanakan pembacaan.
Mula-mula kami perlu memberitahu libelfin cara membaca daftar daripada proses kami. Kami mencipta kelas yang mewarisi daripada expr_context dan menggunakan ptrace untuk mengendalikan segala-galanya:
class ptrace_expr_context : public dwarf::expr_context { public: ptrace_expr_context (pid_t pid) : m_pid{pid} {} dwarf::taddr reg (unsigned regnum) override { return get_register_value_from_dwarf_register(m_pid, regnum); } dwarf::taddr pc() override { struct user_regs_struct regs; ptrace(PTRACE_GETREGS, m_pid, nullptr, ®s); return regs.rip; } dwarf::taddr deref_size (dwarf::taddr address, unsigned size) override { //TODO take into account size return ptrace(PTRACE_PEEKDATA, m_pid, address, nullptr); } private: pid_t m_pid; };
Bacaan akan dikendalikan oleh fungsi read_variables dalam kelas debugger kami:
void debugger::read_variables() { using namespace dwarf; auto func = get_function_from_pc(get_pc()); //... }
Perkara pertama yang kita lakukan di atas ialah mencari fungsi yang kita ada sekarang, maka kita perlu mengulangi entri dalam fungsi itu untuk mencari pembolehubah:
for (const auto& die : func) { if (die.tag == DW_TAG::variable) { //... } }
Kami mendapat maklumat lokasi dengan mencari entri DW_AT_location dalam DIE:
auto loc_val = die[DW_AT::location];
Kemudian kami pastikan ia exprloc dan minta libelfin menilai ungkapan kami:
if (loc_val.get_type() == value::type::exprloc) { ptrace_expr_context context {m_pid}; auto result = loc_val.as_exprloc().evaluate(&context);
Sekarang kita telah menilai ungkapan, kita perlu membaca kandungan pembolehubah. Ia boleh berada dalam ingatan atau daftar, jadi kami akan mengendalikan kedua-dua kes:
switch (result.location_type) { case expr_result::type::address: { auto value = read_memory(result.value); std::cout << at_name(die) << " (0x" << std::hex << result.value << ") = " << value << std::endl; break; } case expr_result::type::reg: { auto value = get_register_value_from_dwarf_register(m_pid, result.value); std::cout << at_name(die) << " (reg " << result.value << ") = " << value << std::endl; break; } default: throw std::runtime_error{"Unhandled variable location"}; }
Anda boleh melihat bahawa berdasarkan jenis pembolehubah, saya mencetak nilai tanpa penjelasan. Mudah-mudahan dengan kod ini anda dapat melihat bagaimana terdapat sokongan untuk menulis pembolehubah, atau mencari pembolehubah dengan nama tertentu.
Akhirnya kami boleh menambah ini pada penghurai arahan kami:
else if(is_prefix(command, "variables")) { read_variables(); }
Tulis beberapa fungsi kecil dengan beberapa pembolehubah, susunkannya tanpa pengoptimuman dan dengan maklumat nyahpepijat, kemudian lihat jika anda boleh membaca nilai pembolehubah itu. Cuba tulis ke alamat memori tempat pembolehubah disimpan dan lihat bagaimana program mengubah tingkah laku.
Sudah ada sembilan artikel, dan yang terakhir tinggal! Lain kali saya akan membincangkan beberapa konsep yang lebih maju yang mungkin menarik minat anda. Kini anda boleh mencari kod untuk siaran ini di sini.
Atas ialah kandungan terperinci Terokai teknik pengendalian berubah-ubah dalam penyahpepijat Linux!. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!