ffi_linux.c raw

   1  //go:build none
   2  
   3  // Minimal ELF shared library loader for mxh FFI.
   4  // Provides dlopen/dlsym without requiring the dynamic linker.
   5  // x86_64 Linux only. Handles simple .so files with R_X86_64_RELATIVE
   6  // and R_X86_64_GLOB_DAT relocations.
   7  
   8  #include <elf.h>
   9  #include <fcntl.h>
  10  #include <stdint.h>
  11  #include <string.h>
  12  #include <sys/mman.h>
  13  #include <unistd.h>
  14  
  15  struct ffi_lib {
  16  	uint8_t *base;
  17  	uint64_t bias;
  18  	Elf64_Sym *symtab;
  19  	const char *strtab;
  20  	uint32_t nsyms;
  21  };
  22  
  23  #define MAX_FFI_LIBS 16
  24  static struct ffi_lib libs[MAX_FFI_LIBS];
  25  static int nlibs;
  26  
  27  static ssize_t full_read(int fd, void *buf, size_t n) {
  28  	size_t done = 0;
  29  	while (done < n) {
  30  		ssize_t r = read(fd, (char *)buf + done, n - done);
  31  		if (r <= 0) return -1;
  32  		done += r;
  33  	}
  34  	return done;
  35  }
  36  
  37  void *dlopen(const char *path, int flags) {
  38  	(void)flags;
  39  	if (!path || nlibs >= MAX_FFI_LIBS) return 0;
  40  
  41  	int fd = open(path, O_RDONLY);
  42  	if (fd < 0) return 0;
  43  
  44  	Elf64_Ehdr ehdr;
  45  	if (full_read(fd, &ehdr, sizeof(ehdr)) < 0) goto fail;
  46  	if (memcmp(ehdr.e_ident, ELFMAG, SELFMAG) != 0) goto fail;
  47  	if (ehdr.e_type != ET_DYN) goto fail;
  48  	if (ehdr.e_machine != EM_X86_64) goto fail;
  49  
  50  	int phnum = ehdr.e_phnum;
  51  	if (phnum > 128) goto fail;
  52  	Elf64_Phdr phdrs[128];
  53  	if (lseek(fd, ehdr.e_phoff, SEEK_SET) < 0) goto fail;
  54  	if (full_read(fd, phdrs, sizeof(Elf64_Phdr) * phnum) < 0) goto fail;
  55  
  56  	uint64_t lo = UINT64_MAX, hi = 0;
  57  	for (int i = 0; i < phnum; i++) {
  58  		if (phdrs[i].p_type != PT_LOAD) continue;
  59  		uint64_t s = phdrs[i].p_vaddr & ~(uint64_t)0xFFF;
  60  		uint64_t e = (phdrs[i].p_vaddr + phdrs[i].p_memsz + 0xFFF) & ~(uint64_t)0xFFF;
  61  		if (s < lo) lo = s;
  62  		if (e > hi) hi = e;
  63  	}
  64  	if (lo >= hi) goto fail;
  65  	uint64_t span = hi - lo;
  66  
  67  	uint8_t *base = mmap(0, span, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
  68  	if (base == MAP_FAILED) goto fail;
  69  
  70  	for (int i = 0; i < phnum; i++) {
  71  		if (phdrs[i].p_type != PT_LOAD) continue;
  72  		uint64_t vaddr = phdrs[i].p_vaddr;
  73  		uint64_t page = vaddr & ~(uint64_t)0xFFF;
  74  		uint64_t off = vaddr - page;
  75  		uint64_t foff = phdrs[i].p_offset - off;
  76  		uint64_t mapsz = (phdrs[i].p_filesz + off + 0xFFF) & ~(uint64_t)0xFFF;
  77  
  78  		int prot = 0;
  79  		if (phdrs[i].p_flags & PF_R) prot |= PROT_READ;
  80  		if (phdrs[i].p_flags & PF_W) prot |= PROT_WRITE;
  81  		if (phdrs[i].p_flags & PF_X) prot |= PROT_EXEC;
  82  
  83  		void *seg = mmap(base + page - lo, mapsz, prot,
  84  			MAP_PRIVATE | MAP_FIXED, fd, foff);
  85  		if (seg == MAP_FAILED) {
  86  			munmap(base, span);
  87  			goto fail;
  88  		}
  89  
  90  		uint64_t memsz_aligned = (phdrs[i].p_memsz + off + 0xFFF) & ~(uint64_t)0xFFF;
  91  		if (memsz_aligned > mapsz) {
  92  			void *bss = mmap(base + page - lo + mapsz,
  93  				memsz_aligned - mapsz, prot,
  94  				MAP_PRIVATE | MAP_FIXED | MAP_ANONYMOUS, -1, 0);
  95  			if (bss == MAP_FAILED) {
  96  				munmap(base, span);
  97  				goto fail;
  98  			}
  99  		}
 100  	}
 101  	close(fd);
 102  	fd = -1;
 103  
 104  	Elf64_Dyn *dyn = 0;
 105  	for (int i = 0; i < phnum; i++) {
 106  		if (phdrs[i].p_type == PT_DYNAMIC) {
 107  			dyn = (Elf64_Dyn *)(base + phdrs[i].p_vaddr - lo);
 108  			break;
 109  		}
 110  	}
 111  
 112  	Elf64_Rela *rela = 0;
 113  	uint64_t relasz = 0;
 114  	Elf64_Rela *jmprel = 0;
 115  	uint64_t pltrelsz = 0;
 116  	Elf64_Sym *symtab = 0;
 117  	const char *strtab = 0;
 118  	uint64_t strsz = 0;
 119  	uint32_t *hashtab = 0;
 120  
 121  	if (dyn) {
 122  		for (int i = 0; dyn[i].d_tag != DT_NULL; i++) {
 123  			switch (dyn[i].d_tag) {
 124  			case DT_RELA:
 125  				rela = (Elf64_Rela *)(base + dyn[i].d_un.d_ptr - lo);
 126  				break;
 127  			case DT_RELASZ:
 128  				relasz = dyn[i].d_un.d_val;
 129  				break;
 130  			case DT_JMPREL:
 131  				jmprel = (Elf64_Rela *)(base + dyn[i].d_un.d_ptr - lo);
 132  				break;
 133  			case DT_PLTRELSZ:
 134  				pltrelsz = dyn[i].d_un.d_val;
 135  				break;
 136  			case DT_SYMTAB:
 137  				symtab = (Elf64_Sym *)(base + dyn[i].d_un.d_ptr - lo);
 138  				break;
 139  			case DT_STRTAB:
 140  				strtab = (const char *)(base + dyn[i].d_un.d_ptr - lo);
 141  				break;
 142  			case DT_STRSZ:
 143  				strsz = dyn[i].d_un.d_val;
 144  				break;
 145  			case DT_HASH:
 146  				hashtab = (uint32_t *)(base + dyn[i].d_un.d_ptr - lo);
 147  				break;
 148  			}
 149  		}
 150  	}
 151  
 152  	// Process .rela.dyn
 153  	if (rela && relasz) {
 154  		int count = relasz / sizeof(Elf64_Rela);
 155  		for (int i = 0; i < count; i++) {
 156  			uint64_t *target = (uint64_t *)(base + rela[i].r_offset - lo);
 157  			int type = ELF64_R_TYPE(rela[i].r_info);
 158  			if (type == 8) { // R_X86_64_RELATIVE
 159  				*target = (uint64_t)(base - lo) + rela[i].r_addend;
 160  			} else if (type == 6 || type == 7) { // GLOB_DAT, JUMP_SLOT
 161  				*target = 0;
 162  			}
 163  		}
 164  	}
 165  
 166  	// Process .rela.plt
 167  	if (jmprel && pltrelsz) {
 168  		int count = pltrelsz / sizeof(Elf64_Rela);
 169  		for (int i = 0; i < count; i++) {
 170  			uint64_t *target = (uint64_t *)(base + jmprel[i].r_offset - lo);
 171  			int type = ELF64_R_TYPE(jmprel[i].r_info);
 172  			if (type == 7) { // JUMP_SLOT
 173  				*target = 0;
 174  			} else if (type == 8) { // RELATIVE
 175  				*target = (uint64_t)(base - lo) + jmprel[i].r_addend;
 176  			}
 177  		}
 178  	}
 179  
 180  	uint32_t nsyms = 0;
 181  	if (hashtab) {
 182  		nsyms = hashtab[1]; // nchain in DT_HASH
 183  	} else if (symtab && strtab && strsz > 0) {
 184  		// No hash table; estimate from strtab position
 185  		nsyms = ((uint8_t *)strtab - (uint8_t *)symtab) / sizeof(Elf64_Sym);
 186  	}
 187  
 188  	struct ffi_lib *lib = &libs[nlibs++];
 189  	lib->base = base;
 190  	lib->bias = lo;
 191  	lib->symtab = symtab;
 192  	lib->strtab = strtab;
 193  	lib->nsyms = nsyms;
 194  	return lib;
 195  
 196  fail:
 197  	if (fd >= 0) close(fd);
 198  	return 0;
 199  }
 200  
 201  void *dlsym(void *handle, const char *name) {
 202  	if (!handle || !name) return 0;
 203  	struct ffi_lib *lib = handle;
 204  	if (!lib->symtab || !lib->strtab || !lib->nsyms) return 0;
 205  
 206  	for (uint32_t i = 0; i < lib->nsyms; i++) {
 207  		Elf64_Sym *sym = &lib->symtab[i];
 208  		if (sym->st_shndx == SHN_UNDEF) continue;
 209  		int type = ELF64_ST_TYPE(sym->st_info);
 210  		if (type != STT_FUNC && type != STT_OBJECT) continue;
 211  		if (strcmp(lib->strtab + sym->st_name, name) == 0) {
 212  			return lib->base + sym->st_value - lib->bias;
 213  		}
 214  	}
 215  	return 0;
 216  }
 217