mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-11-12 17:19:28 +01:00
Line info: Bound the DWARF line program parse
- An extended opcode's length was added to the position unchecked, so a huge one wrapped back to the opcode itself and looped forever (and, with a row in the loop, grew the table until memory ran out). Lengths must now fit in the unit. This runs on every loaded module and on a .elf next to the EBOOT. - Cap the rows at a couple per instruction and the distinct files at 65536, since one-byte opcodes and short names could otherwise make gigabytes of tables from a large file. - ELF and unit bounds checks in 64 bits (they wrapped on 32-bit hosts), the section headers copied out rather than read unaligned, a bounded compare for the section name, and line and SLEB arithmetic that can't overflow. - Rows at the same address now keep the last, as the comment says. std::unique kept the first, so an end-of-sequence row could hide the next sequence's first row. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
366a0744e6
commit
04bdf46bc6
1 file changed
+70
-27
+70
-27
@@ -16,6 +16,7 @@
|
||||
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <map>
|
||||
|
||||
@@ -83,19 +84,20 @@ public:
|
||||
return Fail();
|
||||
}
|
||||
int64_t SLEB() {
|
||||
int64_t result = 0;
|
||||
// Unsigned, so shifting into (and past) the sign bit is defined.
|
||||
uint64_t result = 0;
|
||||
int shift = 0;
|
||||
for (int i = 0; i < 10; i++) {
|
||||
const uint8_t b = U8();
|
||||
if (!ok_)
|
||||
return 0;
|
||||
if (shift < 64)
|
||||
result |= (int64_t)(b & 0x7f) << shift;
|
||||
result |= (uint64_t)(b & 0x7f) << shift;
|
||||
shift += 7;
|
||||
if (!(b & 0x80)) {
|
||||
if (shift < 64 && (b & 0x40))
|
||||
result -= (int64_t)1 << shift;
|
||||
return result;
|
||||
result |= ~(uint64_t)0 << shift;
|
||||
return (int64_t)result;
|
||||
}
|
||||
}
|
||||
return Fail();
|
||||
@@ -147,6 +149,13 @@ enum {
|
||||
|
||||
// One .debug_line unit: header, then a bytecode program that walks a virtual machine whose output
|
||||
// rows are (address, file, line). See the DWARF spec, "Line Number Information".
|
||||
// Line 0 marks the end of a sequence, so real rows are at least 1.
|
||||
static u32 ClampLine(int64_t line) {
|
||||
return line > 0 ? (u32)std::min<int64_t>(line, UINT32_MAX) : 1;
|
||||
}
|
||||
|
||||
static constexpr size_t MAX_FILES = 1 << 16;
|
||||
|
||||
static bool ParseUnit(Reader &r, size_t unitEnd, std::vector<LineEntry> *entries,
|
||||
std::vector<std::string> *files, std::map<std::string, u32> *fileIndex, u32 moduleStart, u32 moduleSize, u32 addressDelta) {
|
||||
const uint16_t version = r.U16();
|
||||
@@ -158,6 +167,8 @@ static bool ParseUnit(Reader &r, size_t unitEnd, std::vector<LineEntry> *entries
|
||||
}
|
||||
|
||||
const uint32_t headerLength = r.U32();
|
||||
if ((u64)r.pos() + headerLength > unitEnd)
|
||||
return false;
|
||||
const size_t programStart = r.pos() + headerLength;
|
||||
|
||||
const uint8_t minInstLength = r.U8();
|
||||
@@ -192,6 +203,11 @@ static bool ParseUnit(Reader &r, size_t unitEnd, std::vector<LineEntry> *entries
|
||||
// Deduped across units so a header included by 500 files is stored once.
|
||||
auto it = fileIndex->find(name);
|
||||
if (it == fileIndex->end()) {
|
||||
// Each one costs far more memory than its bytes in the file.
|
||||
if (files->size() >= MAX_FILES) {
|
||||
unitFiles.push_back(0);
|
||||
continue;
|
||||
}
|
||||
it = fileIndex->insert({ name, (u32)files->size() }).first;
|
||||
files->push_back(name);
|
||||
}
|
||||
@@ -204,8 +220,12 @@ static bool ParseUnit(Reader &r, size_t unitEnd, std::vector<LineEntry> *entries
|
||||
|
||||
u32 address = 0;
|
||||
u32 file = 1;
|
||||
int line = 1;
|
||||
// 64 bits, so the file can't overflow it with line advances.
|
||||
int64_t line = 1;
|
||||
bool sawAddress = false;
|
||||
// Every one-byte special opcode can emit a row. After deduplication there's at most one per
|
||||
// address, so more than a couple per instruction is a file trying to exhaust memory.
|
||||
const size_t maxEntries = (size_t)moduleSize / 2 + 65536;
|
||||
|
||||
auto emit = [&](u32 lineNo) {
|
||||
// Rows before a DW_LNE_set_address belong to no real code, and anything that doesn't land
|
||||
@@ -213,6 +233,8 @@ static bool ParseUnit(Reader &r, size_t unitEnd, std::vector<LineEntry> *entries
|
||||
const u32 finalAddress = addressDelta + address;
|
||||
if (!sawAddress || finalAddress < moduleStart || finalAddress - moduleStart >= moduleSize)
|
||||
return;
|
||||
if (entries->size() >= maxEntries)
|
||||
return;
|
||||
LineEntry e;
|
||||
e.address = finalAddress;
|
||||
e.line = lineNo;
|
||||
@@ -227,10 +249,14 @@ static bool ParseUnit(Reader &r, size_t unitEnd, std::vector<LineEntry> *entries
|
||||
const uint8_t adjusted = op - opcodeBase;
|
||||
address += (adjusted / lineRange) * minInstLength;
|
||||
line += lineBase + (adjusted % lineRange);
|
||||
emit(line > 0 ? (u32)line : 1);
|
||||
emit(ClampLine(line));
|
||||
} else if (op == 0) {
|
||||
// Extended opcode, length-prefixed so unknown ones can be skipped.
|
||||
const uint64_t length = r.ULEB();
|
||||
// The length covers at least the sub-opcode. Check it before adding: a huge one wraps
|
||||
// back to this opcode, which then loops forever.
|
||||
if (!r.ok() || length == 0 || r.pos() > unitEnd || length > unitEnd - r.pos())
|
||||
return false;
|
||||
const size_t next = r.pos() + (size_t)length;
|
||||
const uint8_t sub = r.U8();
|
||||
if (sub == DW_LNE_end_sequence) {
|
||||
@@ -248,13 +274,13 @@ static bool ParseUnit(Reader &r, size_t unitEnd, std::vector<LineEntry> *entries
|
||||
} else {
|
||||
switch (op) {
|
||||
case DW_LNS_copy:
|
||||
emit(line > 0 ? (u32)line : 1);
|
||||
emit(ClampLine(line));
|
||||
break;
|
||||
case DW_LNS_advance_pc:
|
||||
address += (u32)r.ULEB() * minInstLength;
|
||||
break;
|
||||
case DW_LNS_advance_line:
|
||||
line += (int)r.SLEB();
|
||||
line = std::clamp<int64_t>(line + std::clamp<int64_t>(r.SLEB(), INT32_MIN, INT32_MAX), INT32_MIN, UINT32_MAX);
|
||||
break;
|
||||
case DW_LNS_set_file:
|
||||
file = (u32)r.ULEB();
|
||||
@@ -293,36 +319,46 @@ int LineInfoMap::AddModule(std::string_view elfData, u32 moduleStart, u32 module
|
||||
if (data[0] != ELFMAG0 || data[1] != ELFMAG1 || data[2] != ELFMAG2 || data[3] != ELFMAG3)
|
||||
return 0;
|
||||
|
||||
const Elf32_Ehdr *header = (const Elf32_Ehdr *)data;
|
||||
// Copied out, since the offsets in the file needn't be aligned. Sums in 64 bits, so they can't
|
||||
// wrap a 32-bit size_t.
|
||||
Elf32_Ehdr headerCopy;
|
||||
memcpy(&headerCopy, data, sizeof(headerCopy));
|
||||
const Elf32_Ehdr *header = &headerCopy;
|
||||
if (header->e_shoff == 0 || header->e_shnum == 0 || header->e_shentsize < sizeof(Elf32_Shdr))
|
||||
return 0;
|
||||
if ((size_t)header->e_shoff + (size_t)header->e_shnum * header->e_shentsize > size)
|
||||
if ((u64)header->e_shoff + (u64)header->e_shnum * header->e_shentsize > size)
|
||||
return 0;
|
||||
if (header->e_shstrndx >= header->e_shnum)
|
||||
return 0;
|
||||
|
||||
auto section = [&](int i) {
|
||||
return (const Elf32_Shdr *)(data + header->e_shoff + (size_t)i * header->e_shentsize);
|
||||
Elf32_Shdr s;
|
||||
memcpy(&s, data + header->e_shoff + (size_t)i * header->e_shentsize, sizeof(s));
|
||||
return s;
|
||||
};
|
||||
|
||||
const Elf32_Shdr *shstr = section(header->e_shstrndx);
|
||||
if ((size_t)shstr->sh_offset + shstr->sh_size > size)
|
||||
const Elf32_Shdr shstr = section(header->e_shstrndx);
|
||||
if ((u64)shstr.sh_offset + shstr.sh_size > size)
|
||||
return 0;
|
||||
|
||||
const Elf32_Shdr *debugLine = nullptr;
|
||||
static const char debugLineName[] = ".debug_line";
|
||||
Elf32_Shdr debugLine{};
|
||||
bool foundDebugLine = false;
|
||||
for (int i = 0; i < header->e_shnum; i++) {
|
||||
const Elf32_Shdr *s = section(i);
|
||||
if (s->sh_name >= shstr->sh_size)
|
||||
const Elf32_Shdr s = section(i);
|
||||
// The name, with its terminator, has to fit in the string table.
|
||||
if (s.sh_name >= shstr.sh_size || shstr.sh_size - s.sh_name < sizeof(debugLineName))
|
||||
continue;
|
||||
const char *name = (const char *)data + shstr->sh_offset + s->sh_name;
|
||||
if (!strcmp(name, ".debug_line")) {
|
||||
const char *name = (const char *)data + shstr.sh_offset + s.sh_name;
|
||||
if (!memcmp(name, debugLineName, sizeof(debugLineName))) {
|
||||
debugLine = s;
|
||||
foundDebugLine = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!debugLine || debugLine->sh_size == 0)
|
||||
if (!foundDebugLine || debugLine.sh_size == 0)
|
||||
return 0;
|
||||
if ((size_t)debugLine->sh_offset + debugLine->sh_size > size)
|
||||
if ((u64)debugLine.sh_offset + debugLine.sh_size > size)
|
||||
return 0;
|
||||
|
||||
ModuleLines mod;
|
||||
@@ -331,9 +367,9 @@ int LineInfoMap::AddModule(std::string_view elfData, u32 moduleStart, u32 module
|
||||
mod.files.push_back("<unknown>");
|
||||
std::map<std::string, u32> fileIndex;
|
||||
|
||||
Reader r(data + debugLine->sh_offset, debugLine->sh_size);
|
||||
Reader r(data + debugLine.sh_offset, debugLine.sh_size);
|
||||
int skippedUnits = 0;
|
||||
while (r.ok() && !r.AtEnd(debugLine->sh_size)) {
|
||||
while (r.ok() && !r.AtEnd(debugLine.sh_size)) {
|
||||
const size_t unitStart = r.pos();
|
||||
const uint32_t unitLength = r.U32();
|
||||
if (!r.ok() || unitLength == 0)
|
||||
@@ -342,9 +378,9 @@ int LineInfoMap::AddModule(std::string_view elfData, u32 moduleStart, u32 module
|
||||
// produces, and misreading it would walk off into nonsense.
|
||||
if (unitLength >= 0xfffffff0)
|
||||
break;
|
||||
const size_t unitEnd = unitStart + 4 + unitLength;
|
||||
if (unitEnd > debugLine->sh_size)
|
||||
if ((u64)unitStart + 4 + unitLength > debugLine.sh_size)
|
||||
break;
|
||||
const size_t unitEnd = unitStart + 4 + unitLength;
|
||||
|
||||
if (!ParseUnit(r, unitEnd, &mod.entries, &mod.files, &fileIndex, moduleStart, moduleSize, addressDelta))
|
||||
skippedUnits++;
|
||||
@@ -363,9 +399,16 @@ int LineInfoMap::AddModule(std::string_view elfData, u32 moduleStart, u32 module
|
||||
std::stable_sort(mod.entries.begin(), mod.entries.end(), [](const LineEntry &a, const LineEntry &b) {
|
||||
return a.address < b.address;
|
||||
});
|
||||
mod.entries.erase(std::unique(mod.entries.begin(), mod.entries.end(), [](const LineEntry &a, const LineEntry &b) {
|
||||
return a.address == b.address;
|
||||
}), mod.entries.end());
|
||||
// (std::unique would keep the first.)
|
||||
size_t out = 0;
|
||||
for (size_t i = 0; i < mod.entries.size(); i++) {
|
||||
if (out > 0 && mod.entries[out - 1].address == mod.entries[i].address) {
|
||||
mod.entries[out - 1] = mod.entries[i];
|
||||
} else {
|
||||
mod.entries[out++] = mod.entries[i];
|
||||
}
|
||||
}
|
||||
mod.entries.resize(out);
|
||||
|
||||
const int count = (int)mod.entries.size();
|
||||
INFO_LOG(Log::Loader, "Line info: %d rows across %d files for module at %08x",
|
||||
|
||||
Reference in new issue
Block a user