Skip to content

Commit

Permalink
macho: simplify errors arising from parsing DWARF
Browse files Browse the repository at this point in the history
  • Loading branch information
kubkon committed Oct 16, 2024
1 parent 3ad45dc commit 1547173
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 123 deletions.
61 changes: 35 additions & 26 deletions src/MachO/Dwarf.zig
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ fn getOffset(debug_str_offsets: []const u8, base: u64, index: u64, dw_fmt: Dwarf
};
}

const ErrCtx = struct {
object: Object,
macho_file: *MachO,
};

pub const InfoReader = struct {
ctx: Dwarf,
pos: usize = 0,
Expand All @@ -29,10 +34,7 @@ pub const InfoReader = struct {
return p.ctx.debug_info;
}

pub fn readCompileUnitHeader(p: *InfoReader) !union(enum) {
ok: CompileUnitHeader,
wrong_version: Version,
} {
pub fn readCompileUnitHeader(p: *InfoReader, err_ctx: ErrCtx) !CompileUnitHeader {
var length: u64 = try p.readInt(u32);
const is_64bit = length == 0xffffffff;
if (is_64bit) {
Expand All @@ -57,19 +59,25 @@ pub const InfoReader = struct {
.address_size = try p.readByte(),
.debug_abbrev_offset = try p.readOffset(dw_fmt),
},
else => return .{ .wrong_version = version },
else => {
err_ctx.macho_file.base.fatal("{}: unhandled DWARF version: expected 4 or 5, got {d}", .{
err_ctx.object.fmtPath(),
version,
});
return error.InvalidVersion;
},
};
return .{ .ok = .{
return .{
.format = dw_fmt,
.length = length,
.version = version,
.debug_abbrev_offset = other.debug_abbrev_offset,
.address_size = other.address_size,
.unit_type = other.unit_type,
} };
};
}

pub fn seekToDie(p: *InfoReader, code: Code, cuh: CompileUnitHeader, abbrev_reader: *AbbrevReader) !SkipResult {
pub fn seekToDie(p: *InfoReader, code: Code, cuh: CompileUnitHeader, abbrev_reader: *AbbrevReader, err_ctx: ErrCtx) !void {
const cuh_length = math.cast(usize, cuh.length) orelse return error.Overflow;
const end_pos = p.pos + switch (cuh.format) {
.dwarf32 => @as(usize, 4),
Expand All @@ -78,27 +86,28 @@ pub const InfoReader = struct {
while (p.pos < end_pos) {
const di_code = try p.readULEB128(u64);
if (di_code == 0) return error.UnexpectedEndOfFile;
if (di_code == code) return .ok;
if (di_code == code) return;

while (try abbrev_reader.readAttr()) |attr| {
const res = try p.skip(attr.form, cuh);
switch (res) {
.ok => {},
.unknown_form => return res,
}
try p.skip(attr.form, cuh, err_ctx);
}
}
return error.UnexpectedEndOfFile;
}

const SkipResult = union(enum) {
ok,
unknown_form: Form,
};

/// When skipping attributes, we don't really need to be able to handle them all
/// since we only ever care about the DW_TAG_compile_unit.
pub fn skip(p: *InfoReader, form: Form, cuh: CompileUnitHeader) !SkipResult {
pub fn skip(p: *InfoReader, form: Form, cuh: CompileUnitHeader, err_ctx: ErrCtx) !void {
p.skipInner(form, cuh) catch |err| switch (err) {
error.UnhandledForm => {
err_ctx.macho_file.base.fatal("{}: unhandled DW_FORM_* 0x{x}", .{ err_ctx.object.fmtPath(), form });
return error.UnhandledForm;
},
else => |e| return e,
};
}

fn skipInner(p: *InfoReader, form: Form, cuh: CompileUnitHeader) !void {
switch (form) {
dw.FORM.sec_offset,
dw.FORM.ref_addr,
Expand Down Expand Up @@ -168,10 +177,9 @@ pub const InfoReader = struct {
_ = try p.readIndex(form);
},

else => return .{ .unknown_form = form },
} else return .{ .unknown_form = form },
else => return error.UnhandledForm,
} else return error.UnhandledForm,
}
return .ok;
}

pub fn readBlock(p: *InfoReader, form: Form) ![]const u8 {
Expand All @@ -198,18 +206,18 @@ pub const InfoReader = struct {
dw.FORM.data8, dw.FORM.ref8, dw.FORM.ref_sig8 => try p.readInt(u64),
dw.FORM.udata, dw.FORM.ref_udata => try p.readULEB128(u64),
dw.FORM.sdata => @bitCast(try p.readILEB128(i64)),
else => return error.UnhandledConstantForm,
else => return error.UnhandledForm,
};
}

pub fn readIndex(p: *InfoReader, form: Form) !u64 {
return switch (form) {
dw.FORM.strx1, dw.FORM.addrx1 => try p.readByte(),
dw.FORM.strx2, dw.FORM.addrx2 => try p.readInt(u16),
dw.FORM.strx3, dw.FORM.addrx3 => error.UnhandledDwForm,
dw.FORM.strx3, dw.FORM.addrx3 => error.UnhandledForm,
dw.FORM.strx4, dw.FORM.addrx4 => try p.readInt(u32),
dw.FORM.strx, dw.FORM.addrx => try p.readULEB128(u64),
else => return error.UnhandledIndexForm,
else => return error.UnhandledForm,
};
}

Expand Down Expand Up @@ -396,6 +404,7 @@ const std = @import("std");
const Allocator = mem.Allocator;
const Dwarf = @This();
const File = @import("file.zig").File;
const MachO = @import("../MachO.zig");
const Object = @import("Object.zig");

pub const At = u64;
Expand Down
152 changes: 55 additions & 97 deletions src/MachO/Object.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1367,45 +1367,42 @@ fn parseDebugInfo(self: *Object, macho_file: *MachO) !void {

if (dwarf.debug_info.len == 0) return;

self.compile_unit = self.findCompileUnit(gpa, dwarf, macho_file) catch |err| switch (err) {
error.ParseFailed => return error.ParseFailed,
else => |e| {
macho_file.base.fatal("{}: unexpected error when parsing DWARF info: {s}", .{ self.fmtPath(), @errorName(e) });
return error.ParseFailed;
},
self.compile_unit = self.findCompileUnit(gpa, dwarf, macho_file) catch |err| {
switch (err) {
error.UnhandledForm,
error.UnexpectedTag,
error.MissingCompDir,
error.MissingTuName,
error.MissingStrOffsetsBase,
error.InvalidForm,
=> {},
else => |e| macho_file.base.fatal("{}: unexpected error when parsing DWARF info: {s}", .{
self.fmtPath(),
@errorName(e),
}),
}
return error.ParseFailed;
};
}

fn findCompileUnit(self: *Object, gpa: Allocator, dwarf: Dwarf, macho_file: *MachO) !CompileUnit {
var info_reader = Dwarf.InfoReader{ .ctx = dwarf };
var abbrev_reader = Dwarf.AbbrevReader{ .ctx = dwarf };

const cuh = switch (try info_reader.readCompileUnitHeader()) {
.ok => |cuh| cuh,
.wrong_version => |ver| {
macho_file.base.fatal("{}: unhandled or unknown DWARF version detected: {d}", .{ self.fmtPath(), ver });
return error.ParseFailed;
},
};
const cuh = try info_reader.readCompileUnitHeader(.{ .object = self.*, .macho_file = macho_file });
try abbrev_reader.seekTo(cuh.debug_abbrev_offset);

const cu_decl = (try abbrev_reader.readDecl()) orelse return error.UnexpectedEndOfFile;
if (cu_decl.tag != Dwarf.TAG.compile_unit) {
macho_file.base.fatal("{}: unexpected DW_TAG_xxx value detected as the first decl: expected 0x{x}, found 0x{x}", .{
macho_file.base.fatal("{}: unexpected DW_TAG_* value detected as the first decl: expected 0x{x}, got 0x{x}", .{
self.fmtPath(),
Dwarf.TAG.compile_unit,
cu_decl.tag,
});
return error.ParseFailed;
return error.UnexpectedTag;
}

switch (try info_reader.seekToDie(cu_decl.code, cuh, &abbrev_reader)) {
.ok => {},
.unknown_form => |form| {
macho_file.base.fatal("{}: unexpected DW_FORM_xxx value detected: 0x{x}", .{ self.fmtPath(), form });
return error.ParseFailed;
},
}
try info_reader.seekToDie(cu_decl.code, cuh, &abbrev_reader, .{ .object = self.*, .macho_file = macho_file });

const Pos = struct {
pos: usize,
Expand All @@ -1423,100 +1420,61 @@ fn findCompileUnit(self: *Object, gpa: Allocator, dwarf: Dwarf, macho_file: *Mac
};

while (try abbrev_reader.readAttr()) |attr| {
const pos: Pos = .{ .pos = info_reader.pos, .form = attr.form };
switch (attr.at) {
Dwarf.AT.name => saved.tu_name = .{ .pos = info_reader.pos, .form = attr.form },
Dwarf.AT.comp_dir => saved.comp_dir = .{ .pos = info_reader.pos, .form = attr.form },
Dwarf.AT.str_offsets_base => saved.str_offsets_base = .{ .pos = info_reader.pos, .form = attr.form },
Dwarf.AT.name => saved.tu_name = pos,
Dwarf.AT.comp_dir => saved.comp_dir = pos,
Dwarf.AT.str_offsets_base => saved.str_offsets_base = pos,
else => {},
}
switch (try info_reader.skip(attr.form, cuh)) {
.ok => {},
.unknown_form => |form| {
macho_file.base.fatal("{}: unexpected DW_FORM_xxx value detected: 0x{x}", .{ self.fmtPath(), form });
return error.ParseFailed;
},
}
try info_reader.skip(attr.form, cuh, .{ .object = self.*, .macho_file = macho_file });
}

const readDwarfString = struct {
fn readDwarfString(
reader: *Dwarf.InfoReader,
header: Dwarf.CompileUnitHeader,
pos: Pos,
base: u64,
) !union(enum) {
ok: [:0]const u8,
invalid_form: Dwarf.Form,
} {
try reader.seekTo(pos.pos);
switch (pos.form) {
Dwarf.FORM.strp,
Dwarf.FORM.string,
=> return .{ .ok = try reader.readString(pos.form, header) },
Dwarf.FORM.strx,
Dwarf.FORM.strx1,
Dwarf.FORM.strx2,
Dwarf.FORM.strx3,
Dwarf.FORM.strx4,
=> return .{ .ok = try reader.readStringIndexed(pos.form, header, base) },
else => return .{ .invalid_form = pos.form },
}
}
}.readDwarfString;

if (saved.comp_dir == null) {
macho_file.base.fatal("{}: missing DW_AT_comp_dir attribute", .{self.fmtPath()});
return error.ParseFailed;
return error.MissingCompDir;
}
if (saved.tu_name == null) {
macho_file.base.fatal("{}: missing DW_AT_name attribute", .{self.fmtPath()});
return error.ParseFailed;
return error.MissingTuName;
}

const str_offsets_base: ?u64 = if (saved.str_offsets_base) |str_offsets_base| str_offsets_base: {
if (cuh.version < 5) break :str_offsets_base null;
try info_reader.seekTo(str_offsets_base.pos);
break :str_offsets_base try info_reader.readOffset(cuh.format);
} else null;

for (&[_]Pos{ saved.comp_dir.?, saved.tu_name.? }) |pos| {
if (cuh.version >= 5 and needsStrOffsetsBase(pos.form) and str_offsets_base == null) {
macho_file.base.fatal("{}: missing DW_AT_str_offsets_base attribute", .{self.fmtPath()});
return error.ParseFailed;
}
var cu: CompileUnit = .{ .comp_dir = .{}, .tu_name = .{} };
for (&[_]struct { Pos, *MachO.String }{
.{ saved.comp_dir.?, &cu.comp_dir },
.{ saved.tu_name.?, &cu.tu_name },
}) |tuple| {
const pos, const str_offset_ptr = tuple;
try info_reader.seekTo(pos.pos);
str_offset_ptr.* = switch (pos.form) {
Dwarf.FORM.strp,
Dwarf.FORM.string,
=> try self.addString(gpa, try info_reader.readString(pos.form, cuh)),
Dwarf.FORM.strx,
Dwarf.FORM.strx1,
Dwarf.FORM.strx2,
Dwarf.FORM.strx3,
Dwarf.FORM.strx4,
=> blk: {
const base = str_offsets_base orelse {
macho_file.base.fatal("{}: missing DW_AT_str_offsets_base attribute", .{self.fmtPath()});
return error.MissingStrOffsetsBase;
};
break :blk try self.addString(gpa, try info_reader.readStringIndexed(pos.form, cuh, base));
},
else => |form| {
macho_file.base.fatal("{}: invalid DW_FORM_* when parsing string: 0x{x}", .{ self.fmtPath(), form });
return error.InvalidForm;
},
};
}

const comp_dir = switch (try readDwarfString(&info_reader, cuh, saved.comp_dir.?, str_offsets_base orelse 0)) {
.ok => |str| str,
.invalid_form => |form| {
macho_file.base.fatal("{}: invalid form when parsing DWARF string: 0x{x}", .{ self.fmtPath(), form });
return error.ParseFailed;
},
};
const tu_name = switch (try readDwarfString(&info_reader, cuh, saved.tu_name.?, str_offsets_base orelse 0)) {
.ok => |str| str,
.invalid_form => |form| {
macho_file.base.fatal("{}: invalid form when parsing DWARF string: 0x{x}", .{ self.fmtPath(), form });
return error.ParseFailed;
},
};

return .{
.comp_dir = try self.addString(gpa, comp_dir),
.tu_name = try self.addString(gpa, tu_name),
};
}

fn needsStrOffsetsBase(form: Dwarf.Form) bool {
return switch (form) {
Dwarf.FORM.strx,
Dwarf.FORM.strx1,
Dwarf.FORM.strx2,
Dwarf.FORM.strx3,
Dwarf.FORM.strx4,
=> true,
else => false,
};
return cu;
}

pub fn resolveSymbols(self: *Object, macho_file: *MachO) !void {
Expand Down

0 comments on commit 1547173

Please sign in to comment.