|
| 1 | +-- Scan and dump likely vtable addresses |
| 2 | +memscan = require('memscan') |
| 3 | + |
| 4 | +local osType = dfhack.getOSType() |
| 5 | +if osType ~= 'linux' then |
| 6 | + qerror('unsupported OS: ' .. osType) |
| 7 | +end |
| 8 | + |
| 9 | +local df_ranges = {} |
| 10 | +for i,mem in ipairs(dfhack.internal.getMemRanges()) do |
| 11 | + if mem.read and ( |
| 12 | + string.match(mem.name,'/dwarfort%.exe$') |
| 13 | + or string.match(mem.name,'/dwarfort$') |
| 14 | + or string.match(mem.name,'/Dwarf_Fortress$') |
| 15 | + or string.match(mem.name,'Dwarf Fortress%.exe') |
| 16 | + or string.match(mem.name,'/libg_src_lib.so$') |
| 17 | + ) |
| 18 | + then |
| 19 | + table.insert(df_ranges, mem) |
| 20 | + end |
| 21 | +end |
| 22 | + |
| 23 | +function is_df_addr(a) |
| 24 | + for _, mem in ipairs(df_ranges) do |
| 25 | + if a >= mem.start_addr and a < mem.end_addr then |
| 26 | + return true |
| 27 | + end |
| 28 | + end |
| 29 | + return false |
| 30 | +end |
| 31 | + |
| 32 | +for _, range in ipairs(df_ranges) do |
| 33 | + if (not range.read) or range.write or range.execute or range.name:match('g_src') then |
| 34 | + goto next_range |
| 35 | + end |
| 36 | + |
| 37 | + local area = memscan.MemoryArea.new(range.start_addr, range.end_addr) |
| 38 | + for i = 1, area.uintptr_t.count - 1 do |
| 39 | + -- take every pointer-aligned value in memory mapped to the DF executable, and see if it is a valid vtable |
| 40 | + -- start by following the logic in Process::doReadClassName() and ensure it doesn't crash |
| 41 | + local vtable = area.uintptr_t:idx2addr(i) |
| 42 | + local typeinfo = area.uintptr_t[i - 1] |
| 43 | + if is_df_addr(typeinfo + 8) then |
| 44 | + local typestring = df.reinterpret_cast('uintptr_t', typeinfo + 8)[0] |
| 45 | + if is_df_addr(typestring) then |
| 46 | + -- rule out false positives by checking that the vtable points to a table of valid pointers |
| 47 | + -- TODO: check that the pointers are actually function pointers |
| 48 | + local vlen = 0 |
| 49 | + while is_df_addr(vtable + (8*vlen)) and is_df_addr(df.reinterpret_cast('uintptr_t', vtable + (8*vlen))[0]) do |
| 50 | + vlen = vlen + 1 |
| 51 | + break -- for now, any vtable with one valid pointer is valid enough |
| 52 | + end |
| 53 | + if vlen > 0 then |
| 54 | + -- some false positives can be ruled out if the string.char() call in read_c_string() throws an error for invalid characters |
| 55 | + local ok, name = pcall(function() |
| 56 | + return memscan.read_c_string(df.reinterpret_cast('char', typestring)) |
| 57 | + end) |
| 58 | + if ok then |
| 59 | + -- GCC strips the "_Z" prefix from typeinfo names, so add it back |
| 60 | + local demangled_name = dfhack.internal.cxxDemangle('_Z' .. name) |
| 61 | + if demangled_name and not demangled_name:match('[<>]') and not demangled_name:match('^std::') then |
| 62 | + print(("<vtable-address name='%s' value='0x%x'/>"):format(demangled_name, vtable)) |
| 63 | + end |
| 64 | + end |
| 65 | + end |
| 66 | + end |
| 67 | + end |
| 68 | + |
| 69 | + end |
| 70 | + ::next_range:: |
| 71 | +end |
0 commit comments