Skip to content

Commit e17b810

Browse files
committed
Merge remote-tracking branch 'lethosor/scan-vtables'
2 parents 855f0f6 + 3624f0d commit e17b810

File tree

2 files changed

+95
-0
lines changed

2 files changed

+95
-0
lines changed

devel/scan-vtables.lua

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
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

docs/devel/scan-vtables.rst

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
devel/scan-vtables
2+
==================
3+
4+
.. dfhack-tool::
5+
:summary: Scan for and print likely vtable addresses.
6+
:tags: dev
7+
8+
.. warning::
9+
10+
THIS SCRIPT IS STRICTLY FOR DFHACK DEVELOPERS.
11+
12+
Running this script on a new DF version will NOT MAKE IT RUN CORRECTLY if
13+
any data structures changed, thus possibly leading to CRASHES AND/OR
14+
PERMANENT SAVE CORRUPTION.
15+
16+
This script scans for likely vtables in memory pages mapped to the DF
17+
executable, and prints them in a format ready for inclusion in ``symbols.xml``
18+
19+
Usage
20+
-----
21+
22+
::
23+
24+
devel/scan-vtables

0 commit comments

Comments
 (0)