commit 2a542b20b82b7efe260b909d8c95413d87e87b0f
parent 45564c203da1b09d4fd3e5e1a3f9e68d656bbe91
Author: Drew DeVault <sir@cmpwn.com>
Date: Tue, 2 Jan 2024 12:42:14 +0100
debug::image: new module
This module adds support for doing a few common operations around ELF
executables. It exists mainly to support the future debug:: module.
Signed-off-by: Drew DeVault <sir@cmpwn.com>
Diffstat:
7 files changed, 282 insertions(+), 0 deletions(-)
diff --git a/debug/image/README b/debug/image/README
@@ -0,0 +1,6 @@
+This module implements functionality for examining executable files. It provides
+some support code for working with memory-mapped ELF executables.
+
+That this module does not make compatibility guarantees and is subject to change
+in the future should Hare be ported to a target with a different executable
+format.
diff --git a/debug/image/open.ha b/debug/image/open.ha
@@ -0,0 +1,60 @@
+// SPDX-License-Identifier: MPL-2.0
+// (c) Hare authors <https://harelang.org>
+
+use format::elf;
+use format::elf::{shn};
+use io;
+
+export type image = struct {
+ fd: io::file,
+ data: []u8,
+ header: *elf::header64,
+ // Cached sections
+ shstrtab: nullable *elf::section64,
+ symtab: nullable *elf::section64,
+ strtab: nullable *elf::section64,
+ debug_abbr: nullable *elf::section64,
+ debug_aranges: nullable *elf::section64,
+ debug_info: nullable *elf::section64,
+ debug_line: nullable *elf::section64,
+ debug_str: nullable *elf::section64,
+};
+
+// Opens an [[io::file]] as a program image.
+export fn open(
+ file: io::file,
+) (image | io::error) = {
+ const orig = io::tell(file)?;
+ io::seek(file, 0, io::whence::END)?;
+ const length = io::tell(file)?: size;
+ io::seek(file, orig, io::whence::SET)?;
+
+ const base = io::mmap(null, length,
+ io::prot::READ,
+ io::mflag::PRIVATE,
+ file, 0z)?;
+
+ const data = (base: *[*]u8)[..length];
+ const head = base: *elf::header64;
+
+ let shstrtab: nullable *elf::section64 = null;
+ if (head.e_shstrndx != shn::UNDEF) {
+ const shoffs = head.e_shoff + head.e_shstrndx * head.e_shentsize;
+ shstrtab = &data[shoffs]: *elf::section64;
+ };
+
+ return image {
+ fd = file,
+ data = data,
+ header = head,
+ shstrtab = shstrtab,
+ ...
+ };
+};
+
+// Closes a program [[image]].
+export fn close(image: *image) void = {
+ io::munmap(&image.data[0], len(image.data))!;
+ io::close(image.fd)!;
+};
+
diff --git a/debug/image/sections.ha b/debug/image/sections.ha
@@ -0,0 +1,104 @@
+// SPDX-License-Identifier: MPL-2.0
+// (c) Hare authors <https://harelang.org>
+
+use format::elf;
+use format::elf::{sht};
+use types::c;
+use memio;
+
+// Check that this section is actually a reference to this image.
+fn section_validate(image: *image, sec: *elf::section64) void = {
+ const addr = sec: uintptr;
+ const min = &image.data[0]: uintptr;
+ const max = min + len(image.data): uintptr;
+ assert(min <= addr && max > addr, "section_name: invalid section");
+};
+
+// Returns a program section by name. Returns null if there is no such section,
+// or if the section names are not available in this image (e.g. because it was
+// stripped).
+export fn section_byname(
+ image: *image,
+ name: str,
+) nullable *elf::section64 = {
+ const cached = [
+ (".symtab", &image.symtab),
+ (".strtab", &image.strtab),
+ (".debug_abbr", &image.debug_abbr),
+ (".debug_aranges", &image.debug_aranges),
+ (".debug_info", &image.debug_info),
+ (".debug_line", &image.debug_line),
+ (".debug_str", &image.debug_str),
+ ];
+ for (let i = 0z; i < len(cached); i += 1) {
+ const (cand, val) = cached[i];
+ if (cand == name) {
+ match (*val) {
+ case null => break;
+ case let sec: *elf::section64 =>
+ return sec;
+ };
+ };
+ };
+
+ const head = image.header;
+ let r: nullable *elf::section64 = null;
+ for (let i = 0u16; i < head.e_shnum; i += 1) {
+ const shoffs = head.e_shoff + i * head.e_shentsize;
+ const sec = &image.data[shoffs]: *elf::section64;
+ if (sec.sh_type == sht::NULL) {
+ continue;
+ };
+
+ const cand = section_name(image, sec);
+ if (cand == name) {
+ r = sec;
+ break;
+ };
+ };
+
+ match (r) {
+ case null =>
+ return null;
+ case let sec: *elf::section64 =>
+ for (let i = 0z; i < len(cached); i += 1) {
+ const (cand, val) = cached[i];
+ if (cand == name) {
+ *val = sec;
+ };
+ };
+ };
+
+ return r;
+};
+
+// Returns the name of this [[elf::section64]], returning "" if the section
+// names are not available in this image (i.e. it has been stripped).
+export fn section_name(
+ image: *image,
+ sec: *elf::section64,
+) const str = {
+ section_validate(image, sec);
+
+ const shtab = match (image.shstrtab) {
+ case let sec: *elf::section64 =>
+ yield sec;
+ case null =>
+ return "";
+ };
+
+ const offs = shtab.sh_offset + sec.sh_name;
+ return c::tostr(&image.data[offs]: *const c::char)!;
+};
+
+// Returns a slice of the data contained with a given section.
+export fn section_data(image: *image, sec: *elf::section64) []u8 = {
+ section_validate(image, sec);
+ return image.data[sec.sh_offset..sec.sh_offset+sec.sh_size];
+};
+
+// Returns a [[memio::fixed]] reader for the given section.
+export fn section_reader(image: *image, sec: *elf::section64) memio::stream = {
+ const data = section_data(image, sec);
+ return memio::fixed(data);
+};
diff --git a/debug/image/self+freebsd.ha b/debug/image/self+freebsd.ha
@@ -0,0 +1,48 @@
+// SPDX-License-Identifier: MPL-2.0
+// (c) Hare authors <https://harelang.org>
+
+use errors;
+use fs;
+use io;
+use os;
+use path;
+use rt;
+use types::c;
+
+// Opens the executing process's binary image.
+export fn self() (image | io::error | fs::error) = {
+ // 1: sysctl
+ let buf: [path::MAX * 2 + 1]u8 = [0...];
+ let pathsz = len(buf);
+ match (rt::sysctlbyname("kern.proc.pathname",
+ &buf[0], &pathsz, null, 0)) {
+ case rt::errno => void;
+ case void =>
+ const file = os::open(c::tostr(&buf[0]: *const c::char)!)?;
+ match (open(file)) {
+ case let img: image =>
+ return img;
+ case let err: io::error =>
+ return err;
+ case errors::invalid =>
+ abort("Running program image is not a valid ELF file");
+ };
+ };
+
+ // 2. procfs (not mounted by default, but better than step 3)
+ match (os::open("/proc/curproc/exe")) {
+ case let file: io::file =>
+ match (open(file)) {
+ case let img: image =>
+ return img;
+ case let err: io::error =>
+ return err;
+ case errors::invalid =>
+ abort("Running program image is not a valid ELF file");
+ };
+ case => void;
+ };
+
+ // 3. Fallback (os::args[0])
+ return self_argv();
+};
diff --git a/debug/image/self+linux.ha b/debug/image/self+linux.ha
@@ -0,0 +1,27 @@
+// SPDX-License-Identifier: MPL-2.0
+// (c) Hare authors <https://harelang.org>
+
+use errors;
+use fs;
+use io;
+use os;
+
+// Opens the executing process's binary image.
+export fn self() (image | io::error | fs::error) = {
+ const file = match (os::open("/proc/self/exe")) {
+ case let file: io::file =>
+ yield file;
+ case =>
+ // procfs may not be available, try fallback
+ return self_argv();
+ };
+
+ match (open(file)) {
+ case let img: image =>
+ return img;
+ case let err: io::error =>
+ return err;
+ case errors::invalid =>
+ abort("Running program image is not a valid ELF file");
+ };
+};
diff --git a/debug/image/self+openbsd.ha b/debug/image/self+openbsd.ha
@@ -0,0 +1,11 @@
+// SPDX-License-Identifier: MPL-2.0
+// (c) Hare authors <https://harelang.org>
+
+use fs;
+use io;
+
+// Opens the executing process's binary image.
+export fn self() (image | io::error | fs::error) = {
+ // OpenBSD only supports the fallback approach.
+ return self_argv();
+};
diff --git a/debug/image/self_argv.ha b/debug/image/self_argv.ha
@@ -0,0 +1,26 @@
+// SPDX-License-Identifier: MPL-2.0
+// (c) Hare authors <https://harelang.org>
+
+use errors;
+use fs;
+use io;
+use os;
+use os::exec;
+
+// Fallback implementation of self() that performs path resolution on argv[0]
+fn self_argv() (image | io::error | fs::error) = {
+ match (exec::lookup(os::args[0])) {
+ case let path: str =>
+ const file = os::open(path)?;
+ match (open(file)) {
+ case let img: image =>
+ return img;
+ case let err: io::error =>
+ return err;
+ case errors::invalid =>
+ abort("Running program image is not a valid ELF file");
+ };
+ case void =>
+ return errors::noentry: fs::error;
+ };
+};