Skip to content

Commit 84025b1

Browse files
authored
Merge pull request #1396 from myk002/myk_scan_vtables
[devel/scan-vtables] clean up logic and ensure scan order
2 parents fc67fdc + 3f42783 commit 84025b1

1 file changed

Lines changed: 59 additions & 34 deletions

File tree

devel/scan-vtables.lua

Lines changed: 59 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,74 @@
11
-- Scan and dump likely vtable addresses
2-
memscan = require('memscan')
2+
local memscan = require('memscan')
33

44
local osType = dfhack.getOSType()
55
if osType ~= 'linux' then
66
qerror('unsupported OS: ' .. osType)
77
end
88

9-
local df_ranges = {}
10-
for _, 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)
9+
local function get_ranges()
10+
local df_ranges, lib_names = {}, {}
11+
12+
local raw_ranges = dfhack.internal.getMemRanges()
13+
14+
-- add main binary mem ranges first
15+
for _, range in ipairs(raw_ranges) do
16+
if range.read and (
17+
string.match(range.name, '/dwarfort$') or
18+
string.match(range.name, 'Dwarf Fortress%.exe')
19+
)
20+
then
21+
table.insert(df_ranges, range)
22+
end
23+
end
24+
25+
for _, range in ipairs(raw_ranges) do
26+
if range.read and string.match(range.name, '/libg_src_lib.so$') then
27+
table.insert(df_ranges, range)
28+
lib_names[range.name] = true
29+
end
30+
end
31+
32+
return df_ranges, lib_names
33+
end
34+
35+
local df_ranges, lib_names = get_ranges()
36+
37+
-- vtables that cross a range boundary can appear twice, a truncated version in the
38+
-- lower memory range and a full version in the higher memory range
39+
-- therefore, sort the memory ranges by start address, descending
40+
-- but keep libraries last
41+
local function sort_ranges(a, b)
42+
if lib_names[a.name] == lib_names[b.name] then
43+
return a.start_addr > b.start_addr
2044
end
45+
return lib_names[b.name]
2146
end
2247

48+
table.sort(df_ranges, sort_ranges)
49+
2350
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
51+
for _, range in ipairs(df_ranges) do
52+
if a >= range.start_addr and a < range.end_addr then
2653
return true
2754
end
2855
end
2956
return false
3057
end
3158

32-
local names = {}
59+
local function is_vtable_range(range)
60+
return not range.write and not range.execute
61+
end
3362

34-
function scan_ranges(g_src)
63+
function scan_ranges()
3564
local vtables = {}
36-
for _, range in ipairs(df_ranges) do
37-
if (not range.read) or range.write or range.execute then
38-
goto next_range
39-
end
40-
if not not range.name:match('g_src') ~= g_src then
41-
goto next_range
42-
end
65+
local seen = {} -- only record the first encountered vtable for each name
4366

67+
for _, range in ipairs(df_ranges) do
68+
if not is_vtable_range(range) then goto next_range end
4469
local base = range.name:match('.*/(.*)$')
4570
local area = memscan.MemoryArea.new(range.start_addr, range.end_addr)
71+
local is_lib = lib_names[range.name]
4672
for i = 1, area.uintptr_t.count - 1 do
4773
-- take every pointer-aligned value in memory mapped to the DF executable, and see if it is a valid vtable
4874
-- start by following the logic in Process::doReadClassName() and ensure it doesn't crash
@@ -71,27 +97,26 @@ function scan_ranges(g_src)
7197
if demangled_name and
7298
not demangled_name:match('[<>]') and
7399
not demangled_name:match('^std::') and
74-
not names[demangled_name] and
75-
(g_src or demangled_name ~= 'widgets::widget') -- the widget in g_src takes precedence
100+
not seen[demangled_name] and
101+
(is_lib or demangled_name ~= 'widgets::widget') -- the widget in g_src takes precedence
76102
then
77103
local base_str = ''
78-
if g_src then
104+
if is_lib then
79105
vtable = vtable - range.base_addr
80106
base_str = (" base='%s'"):format(base)
81107
end
82108
vtables[demangled_name] = {value=vtable, base_str=base_str}
109+
seen[demangled_name] = true
83110
end
84111
::next_ptr::
85112
end
86113
::next_range::
87114
end
88-
for name, data in pairs(vtables) do
89-
if not names[name] then
90-
print(("<vtable-address name='%s' value='0x%x'%s/>"):format(name, data.value, data.base_str))
91-
names[name] = true
92-
end
93-
end
115+
116+
return vtables
94117
end
95118

96-
scan_ranges(false)
97-
scan_ranges(true)
119+
local vtables = scan_ranges()
120+
for name, data in pairs(vtables) do
121+
print(("<vtable-address name='%s' value='0x%x'%s/>"):format(name, data.value, data.base_str))
122+
end

0 commit comments

Comments
 (0)