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