Skip to content

Commit 03ecc35

Browse files
committed
idaloader: parse pdata manually before calling eh_parse
results in funcs being marked much sooner, and more funcs identified
1 parent 77295a6 commit 03ecc35

File tree

1 file changed

+108
-19
lines changed

1 file changed

+108
-19
lines changed

idaloader.cpp

Lines changed: 108 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -173,34 +173,100 @@ void pe_add_sections(XEXFile& file)
173173
}
174174
}
175175

176-
void pe_setup_netnode(XEXFile& file)
176+
void pe_parse_pdata(XEXFile& file)
177177
{
178-
netnode penode;
179-
penode.create(PE_NODE);
178+
// Hybrid pdata parser:
179+
// - our code will first read from pdata and create functions based on it
180+
// - then defer to eh_parse which will create .pdata dwords & set up exception handlers
181+
// From testing this results in more functions getting created, and allows autoanalysis to parse functions much earlier
180182

181-
const uint8_t* pe_data = file.pe_data();
182-
IMAGE_DOS_HEADER* dos_header = (IMAGE_DOS_HEADER*)pe_data;
183+
bool has_pdata = false;
183184

184-
// Set PE header node data
185-
// Make a copy of PE header and update with correct values first, since IDA/eh_parse reads some info from this
186-
IMAGE_NT_HEADERS nt_header = *(IMAGE_NT_HEADERS*)(pe_data + dos_header->AddressOfNewExeHeader);
187-
nt_header.OptionalHeader.ImageBase = file.base_address();
185+
// Try reading & marking functions from .pdata section
186+
for (const auto& section : file.sections())
187+
{
188+
// New buffer for section name so we can null-terminate it
189+
char name[9];
190+
std::copy_n(section.Name, 8, name);
191+
name[8] = '\0';
192+
if (strcmp(name, ".pdata"))
193+
continue;
188194

189-
penode.set(&nt_header, sizeof(IMAGE_NT_HEADERS));
195+
has_pdata = true;
190196

191-
// Update imagebase
192-
penode.altset(PE_ALT_IMAGEBASE, file.base_address());
197+
uint32 sec_addr = section.VirtualAddress;
198+
uint32 sec_size = section.VirtualSize;
199+
if (file.header().Magic == MAGIC_XEX3F || file.header().Magic == MAGIC_XEX0)
200+
{
201+
sec_addr = section.PointerToRawData;
202+
sec_size = section.SizeOfRawData; // TODO: verify this?
203+
}
204+
ea_t seg_addr = (ea_t)file.base_address() + (ea_t)section.VirtualAddress;
205+
// Size could be beyond file bounds, if so fix the size to what we can fit
206+
if (sec_addr + sec_size > file.image_size())
207+
sec_size = file.image_size() - sec_addr;
193208

194-
bool has_pdata = false;
195-
for (const auto& section : file.sections())
196-
{
197-
if (!strncmp(section.Name, ".pdata", 6)) {
198-
has_pdata = true;
199-
break;
209+
struct RUNTIME_FUNCTION_INFO // bitfield portion of RUNTIME_FUNCTION
210+
{
211+
uint32_t PrologLength : 8;
212+
uint32_t FunctionLength : 22;
213+
uint32_t FunctionType : 2;
214+
inline xex::RuntimeFunctionType RuntimeFunctionType() {
215+
return (xex::RuntimeFunctionType)FunctionType;
216+
}
217+
};
218+
219+
// Read function addrs from .pdata into vector so we can get a count before asking IDA to create functions for them
220+
std::unordered_map<uint32_t, RUNTIME_FUNCTION_INFO> funcs;
221+
int offset = 0;
222+
while (offset < sec_size)
223+
{
224+
auto* fn_ptr = reinterpret_cast<const xe::be<uint32_t>*>(file.pe_data() + sec_addr + offset);
225+
uint32_t fn_ea = fn_ptr[0];
226+
if (fn_ea)
227+
{
228+
uint32_t fn_info_raw = fn_ptr[1]; // endian-swap the field for us
229+
RUNTIME_FUNCTION_INFO fn_info = *(RUNTIME_FUNCTION_INFO*)&fn_info_raw; // and then convert to RUNTIME_FUNCTION_INFO
230+
funcs.insert({ fn_ea, fn_info });
231+
}
232+
offset += 8;
233+
}
234+
235+
msg("Parsing .pdata and creating %d functions...\n", int(funcs.size()));
236+
237+
// display messagebox prompt to user so they can cancel if needed
238+
show_wait_box("Marking functions from .pdata... (0/%d)", int(funcs.size()));
239+
size_t num = 0;
240+
for (auto& kvp : funcs)
241+
{
242+
if (kvp.second.FunctionLength && !get_fchunk(kvp.first))
243+
{
244+
if (create_insn(kvp.first))
245+
{
246+
show_auto(kvp.first);
247+
// Don't create function for savevmx/restvmx, we handle it in label_regsaveloads
248+
auto fn_type = kvp.second.RuntimeFunctionType();
249+
if (fn_type != xex::RuntimeFunctionType::SaveMillicode && fn_type != xex::RuntimeFunctionType::RestoreMillicode)
250+
{
251+
func_t fn(kvp.first, kvp.first + (kvp.second.FunctionLength * 4));
252+
add_func_ex(&fn);
253+
}
254+
}
255+
}
256+
if (user_cancelled())
257+
break;
258+
// update every few funcs
259+
if (++num % 50 == 0)
260+
{
261+
replace_wait_box("Marking functions from .pdata... (%d/%d)", int(num), int(funcs.size()));
262+
}
200263
}
264+
265+
hide_wait_box();
266+
break;
201267
}
202268

203-
// Ask eh_parse to parse .pdata for us
269+
// Ask eh_parse to parse .pdata, for it to mark xrefs & EH
204270
// Load it here early so user won't need to wait for autoanalysis to complete
205271
if (has_pdata)
206272
{
@@ -211,7 +277,30 @@ void pe_setup_netnode(XEXFile& file)
211277
run_plugin(plugin, 0);
212278
}
213279
}
280+
}
281+
282+
void pe_setup_netnode(XEXFile& file)
283+
{
284+
netnode penode;
285+
penode.create(PE_NODE);
286+
287+
const uint8_t* pe_data = file.pe_data();
288+
IMAGE_DOS_HEADER* dos_header = (IMAGE_DOS_HEADER*)pe_data;
289+
290+
// Set PE header node data
291+
// Make a copy of PE header and update with correct values first, since IDA/eh_parse reads some info from this
292+
IMAGE_NT_HEADERS nt_header = *(IMAGE_NT_HEADERS*)(pe_data + dos_header->AddressOfNewExeHeader);
293+
nt_header.OptionalHeader.ImageBase = file.base_address();
294+
295+
penode.set(&nt_header, sizeof(IMAGE_NT_HEADERS));
296+
297+
// Update imagebase
298+
penode.altset(PE_ALT_IMAGEBASE, file.base_address());
299+
300+
// Parse pdata if it exists
301+
pe_parse_pdata(file);
214302

303+
// Update IDA with any codeview data
215304
size_t cv_length = 0;
216305
auto* cv_data = file.codeview_data(0, &cv_length);
217306
if (cv_data)

0 commit comments

Comments
 (0)