Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/pkg
Path: blob/main/libpkg/pkg_abi_macho.c
2065 views
1
/*-
2
* Copyright (c) 2024 Keve Müller <[email protected]>
3
*
4
* SPDX-License-Identifier: BSD-2-Clause
5
*/
6
7
#include <errno.h>
8
9
#include "private/binfmt.h"
10
#include "private/binfmt_macho.h"
11
#include "private/pkg.h"
12
#include "private/event.h"
13
#include "private/pkg_abi.h"
14
15
/**
16
* Routines to support pkg_abi.c functions when dealing with Mach-O files.
17
* Supports getting struct pkg_abi from the binary's load commands.
18
* Supports getting shared libary information (needed, provided & loader).
19
* Picks right binary in Universal binary based on ABI.
20
*/
21
22
static enum pkg_arch
23
cputype_to_pkg_arch(const cpu_type_subtype_t cpu)
24
{
25
switch (cpu.type) {
26
case CPU_TYPE_ARM:
27
if (cpu.type_is64_32) {
28
return (PKG_ARCH_UNKNOWN); /* aarch64-x32 */
29
} else if (cpu.type_is64) {
30
return (PKG_ARCH_AARCH64);
31
} else {
32
switch (cpu.subtype_arm) {
33
case CPU_SUBTYPE_ARM_V7:
34
case CPU_SUBTYPE_ARM_V7S:
35
case CPU_SUBTYPE_ARM_V7K:
36
case CPU_SUBTYPE_ARM_V7M:
37
case CPU_SUBTYPE_ARM_V7EM:
38
return (PKG_ARCH_ARMV7);
39
case CPU_SUBTYPE_ARM_V6:
40
case CPU_SUBTYPE_ARM_V6M:
41
return (PKG_ARCH_ARMV6);
42
case CPU_SUBTYPE_ARM_XSCALE:
43
case CPU_SUBTYPE_ARM_V5:
44
case CPU_SUBTYPE_ARM_V4T:
45
case CPU_SUBTYPE_ARM_ALL:
46
default:
47
return (PKG_ARCH_UNKNOWN);
48
}
49
}
50
case CPU_TYPE_POWERPC:
51
if (cpu.type_is64_32) {
52
return (PKG_ARCH_UNKNOWN); /* powerpc64-x32 */
53
} else if (cpu.type_is64) {
54
return (PKG_ARCH_POWERPC64);
55
} else {
56
return (PKG_ARCH_POWERPC);
57
}
58
case CPU_TYPE_X86:
59
if (cpu.type_is64_32) {
60
return (PKG_ARCH_UNKNOWN); /* amd64-x32 */
61
} else if (cpu.type_is64) {
62
return (PKG_ARCH_AMD64);
63
} else {
64
return (PKG_ARCH_I386);
65
}
66
default:
67
return (PKG_ARCH_UNKNOWN);
68
}
69
}
70
71
static cpu_type_subtype_t
72
pkg_arch_to_cputype(enum pkg_arch arch) {
73
cpu_type_subtype_t cpu = { 0 };
74
75
switch (arch) {
76
case PKG_ARCH_AARCH64:
77
cpu.type = CPU_TYPE_ARM;
78
cpu.type_is64 = true;
79
break;
80
case PKG_ARCH_AMD64:
81
cpu.type = CPU_TYPE_X86;
82
cpu.type_is64 = true;
83
cpu.subtype_x86 = CPU_SUBTYPE_X86_ALL;
84
break;
85
case PKG_ARCH_ARMV6:
86
cpu.type = CPU_TYPE_ARM;
87
cpu.subtype_arm = CPU_SUBTYPE_ARM_V6;
88
break;
89
case PKG_ARCH_ARMV7:
90
cpu.type = CPU_TYPE_ARM;
91
cpu.subtype_arm = CPU_SUBTYPE_ARM_V7;
92
break;
93
case PKG_ARCH_I386:
94
cpu.type = CPU_TYPE_X86;
95
cpu.subtype_x86 = CPU_SUBTYPE_X86_ALL;
96
break;
97
case PKG_ARCH_POWERPC:
98
cpu.type = CPU_TYPE_POWERPC;
99
cpu.subtype_ppc = CPU_SUBTYPE_POWERPC_ALL;
100
break;
101
case PKG_ARCH_POWERPC64:
102
cpu.type = CPU_TYPE_POWERPC;
103
cpu.type_is64 = true;
104
cpu.subtype_ppc = CPU_SUBTYPE_POWERPC_ALL;
105
break;
106
case PKG_ARCH_POWERPC64LE:
107
case PKG_ARCH_RISCV32:
108
case PKG_ARCH_RISCV64:
109
case PKG_ARCH_UNKNOWN:
110
cpu.type = CPU_TYPE_ANY;
111
break;
112
}
113
114
return cpu;
115
}
116
117
118
/**
119
* Using the passed mf descriptor, match the best entry using the provided hint.
120
* No hint or no architecture in hint -> first entry. Debug1 warning if this is not precise match (there were multiple to choose from)
121
* Hint -> always match, even if single architecture in file. Notice if match fails and return null.
122
*/
123
static const fat_arch_t *
124
match_entry(macho_file_t *mf, enum pkg_arch arch_hint)
125
{
126
const fat_arch_t *p = mf->arch;
127
if (arch_hint != PKG_ARCH_UNKNOWN) {
128
const cpu_type_subtype_t cpu_hint = pkg_arch_to_cputype(arch_hint);
129
const fat_arch_t *p_end = p + mf->narch;
130
while (p < p_end) {
131
// do not match cpu_hint.type == CPU_TYPE_ANY which is used if the
132
// arch_hint was not recognized
133
if (p->cpu.type == cpu_hint.type &&
134
p->cpu.type_is64 == cpu_hint.type_is64) {
135
switch (cpu_hint.type) {
136
case CPU_TYPE_ARM:
137
if (p->cpu.subtype_arm ==
138
CPU_SUBTYPE_ARM_ALL ||
139
cpu_hint.subtype_arm ==
140
CPU_SUBTYPE_ARM_ALL ||
141
p->cpu.subtype_arm ==
142
cpu_hint.subtype_arm) {
143
return p;
144
}
145
break;
146
case CPU_TYPE_POWERPC:
147
if (p->cpu.subtype_ppc ==
148
CPU_SUBTYPE_POWERPC_ALL ||
149
cpu_hint.subtype_ppc ==
150
CPU_SUBTYPE_POWERPC_ALL ||
151
p->cpu.subtype_ppc ==
152
cpu_hint.subtype_ppc) {
153
return p;
154
}
155
break;
156
case CPU_TYPE_X86:
157
if (p->cpu.subtype_x86 ==
158
CPU_SUBTYPE_X86_ALL ||
159
cpu_hint.subtype_x86 ==
160
CPU_SUBTYPE_X86_ALL ||
161
p->cpu.subtype_x86 ==
162
cpu_hint.subtype_x86) {
163
return p;
164
}
165
break;
166
default:
167
break;
168
}
169
}
170
pkg_debug(1, "Looking for %s, did not match %s",
171
pkg_arch_to_string(PKG_OS_DARWIN, arch_hint),
172
pkg_arch_to_string(PKG_OS_DARWIN, cputype_to_pkg_arch(p->cpu)));
173
p++;
174
}
175
pkg_emit_notice("Scanned %d entr%s, found none matching selector %s",
176
mf->narch, mf->narch > 1 ? "ies" : "y",
177
pkg_arch_to_string(PKG_OS_DARWIN, arch_hint));
178
return 0;
179
} else if (mf->narch > 1 ) {
180
pkg_debug(1,"Found %"PRIu32" entries in universal binary, picking first",
181
mf->narch);
182
}
183
return p;
184
}
185
186
/**
187
* With a not-null, potentially pre-populated os_info structure, fill
188
* all members of os_info except altabi with values obtained by parsing the Mach-O
189
* file passed with file descriptor.
190
*
191
* The arch_hint is used to determine the fat entry to be parsed in a universal
192
* binary. If arch_hint is PKG_ARCH_UNKNOWN, the first entry is used.
193
*
194
* Returns EPKG_OK if all went fine, EPKG_FATAL if anything went wrong.
195
* Seeks the file descriptor to an arbitrary position.
196
*/
197
int
198
pkg_macho_abi_from_fd(int fd, struct pkg_abi *abi, enum pkg_arch arch_hint)
199
{
200
*abi = (struct pkg_abi){0};
201
202
ssize_t x;
203
pkg_error_t ret = EPKG_FATAL;
204
205
macho_file_t *mf = 0;
206
build_version_t *bv = 0;
207
208
if ((x = read_macho_file(fd, &mf)) < 0) {
209
goto cleanup;
210
}
211
212
const fat_arch_t *p = match_entry(mf, arch_hint);
213
214
if (!p) {
215
goto cleanup;
216
}
217
218
if (-1 == (x = lseek(fd, p->offset, SEEK_SET))) {
219
goto cleanup;
220
}
221
size_t n = 0;
222
macho_header_t mh;
223
if ((x = read_macho_header(fd, &mh)) < 0) {
224
goto cleanup;
225
}
226
const bool swap = mh.swap;
227
n = 0;
228
for (uint32_t ui = mh.ncmds; ui-- > 0;) {
229
size_t n0 = n;
230
uint32_t loadcmdtype;
231
uint32_t loadcmdsize;
232
READ(u32, loadcmdtype);
233
READ(u32, loadcmdsize);
234
enum MachOLoadCommand loadcmd = loadcmdtype & ~LC_REQ_DYLD;
235
switch (loadcmd) {
236
case LC_BUILD_VERSION:
237
if (bv) { // overwrite previous LC_VERSION_MIN_X
238
// values
239
free(bv);
240
bv = 0;
241
}
242
READ(build_version, bv);
243
break;
244
case LC_VERSION_MIN_IPHONEOS:
245
case LC_VERSION_MIN_MACOSX:
246
case LC_VERSION_MIN_TVOS:
247
case LC_VERSION_MIN_WATCHOS:
248
if (!bv) {
249
if ((x = read_min_version(fd, swap, loadcmd,
250
&bv)) < 0) {
251
goto cleanup;
252
}
253
n += x;
254
break;
255
}
256
// have seen the more precise
257
// LC_BUILD_VERSION already
258
// fall through and disregard this
259
default:
260
break;
261
}
262
const uint32_t fill = loadcmdsize - (n - n0);
263
if (fill && -1 == (x = lseek(fd, fill, SEEK_CUR))) {
264
goto cleanup;
265
}
266
n += fill;
267
if (n > mh.sizeofcmds) {
268
// we passed the frame boundary of the load commands
269
pkg_emit_error("Mach-O structure misread.");
270
errno = EINVAL;
271
goto cleanup;
272
}
273
}
274
275
if (bv) {
276
macho_version_t darwin;
277
map_platform_to_darwin(&darwin, bv->platform, bv->minos);
278
279
abi->os = PKG_OS_DARWIN;
280
281
abi->major = darwin.major;
282
abi->minor = darwin.minor;
283
abi->patch = darwin.patch;
284
285
abi->arch = cputype_to_pkg_arch(mh.cpu);
286
287
if (abi->arch == PKG_ARCH_UNKNOWN) {
288
ret = EPKG_FATAL;
289
} else {
290
ret = EPKG_OK;
291
}
292
} else {
293
pkg_emit_notice("No OS version information found in binary.");
294
ret = EPKG_WARN;
295
}
296
297
cleanup:
298
free(bv);
299
free(mf);
300
return ret;
301
}
302
303
static int
304
analyse_macho(int fd, struct pkg *pkg, char **provided,
305
enum pkg_shlib_flags *provided_flags)
306
{
307
assert(*provided == NULL);
308
assert(*provided_flags == PKG_SHLIB_FLAGS_NONE);
309
310
ssize_t x;
311
pkg_error_t ret = EPKG_END;
312
313
macho_file_t *mf = 0;
314
315
if ((x = read_macho_file(fd, &mf)) < 0) {
316
goto cleanup;
317
}
318
319
const fat_arch_t *p = match_entry(mf, ctx.abi.arch);
320
321
if (!p) {
322
goto cleanup;
323
}
324
325
if (-1 == (x = lseek(fd, p->offset, SEEK_SET))) {
326
goto cleanup;
327
}
328
size_t n = 0;
329
macho_header_t mh;
330
if ((x = read_macho_header(fd, &mh)) < 0) {
331
goto cleanup;
332
}
333
const bool swap = mh.swap;
334
n = 0;
335
for (uint32_t ui = mh.ncmds; ui-- > 0;) {
336
size_t n0 = n;
337
uint32_t loadcmdtype;
338
uint32_t loadcmdsize;
339
READ(u32, loadcmdtype);
340
READ(u32, loadcmdsize);
341
enum MachOLoadCommand loadcmd = loadcmdtype & ~LC_REQ_DYLD;
342
switch (loadcmd) {
343
case LC_RPATH:
344
case LC_LOAD_DYLINKER:;
345
char *dylinker = 0;
346
if ((x = read_path(fd, swap, loadcmdsize,
347
&dylinker)) < 0) {
348
goto cleanup;
349
}
350
n += x;
351
pkg_debug(3, "load_dylinker %d: %s\n", loadcmd, dylinker);
352
free(dylinker);
353
break;
354
case LC_ID_DYLIB: // provides
355
case LC_LOAD_DYLIB: // requires...
356
case LC_LOAD_WEAK_DYLIB:
357
case LC_REEXPORT_DYLIB:
358
case LC_LAZY_LOAD_DYLIB:
359
case LC_LOAD_UPWARD_DYLIB:;
360
dylib_t *dylib = 0;
361
if ((x = read_dylib(fd, swap, loadcmdsize,
362
&dylib)) < 0) {
363
goto cleanup;
364
}
365
n += x;
366
// while under Darwin full path references are recommended and ubiquitous,
367
// we align with pkg native environment and use only the basename
368
// this also strips off any @executable_path, @loader_path, @rpath components
369
const char * basename = strrchr(dylib->path, '/');
370
basename = basename ? basename + 1 : dylib->path;
371
pkg_debug(3,
372
"Adding dynamic library path: %s ts %"PRIu32" current(%"PRIuFAST16", %"PRIuFAST16", %"PRIuFAST16") compat(%"PRIuFAST16", %"PRIuFAST16", %"PRIuFAST16")\n",
373
dylib->path, dylib->timestamp,
374
dylib->current_version.major,
375
dylib->current_version.minor,
376
dylib->current_version.patch,
377
dylib->compatibility_version.major,
378
dylib->compatibility_version.minor,
379
dylib->compatibility_version.patch);
380
381
char *lib_with_version;
382
if (dylib->current_version.patch) {
383
xasprintf(&lib_with_version, "%s-%"PRIuFAST16".%"PRIuFAST16".%"PRIuFAST16, basename, dylib->current_version.major, dylib->current_version.minor, dylib->current_version.patch);
384
} else {
385
xasprintf(&lib_with_version, "%s-%"PRIuFAST16".%"PRIuFAST16, basename, dylib->current_version.major, dylib->current_version.minor);
386
}
387
if (LC_ID_DYLIB == loadcmd) {
388
if (*provided != NULL) {
389
pkg_emit_error("malformed Macho-O file has multiple LC_ID_DYLIB entries");
390
goto cleanup;
391
}
392
*provided = xstrdup(lib_with_version);
393
} else {
394
pkg_addshlib_required(pkg, lib_with_version, PKG_SHLIB_FLAGS_NONE);
395
}
396
free(lib_with_version);
397
free(dylib);
398
break;
399
default:
400
break;
401
}
402
const uint32_t fill = loadcmdsize - (n - n0);
403
if (fill && -1 == (x = lseek(fd, fill, SEEK_CUR))) {
404
goto cleanup;
405
}
406
n += fill;
407
if (n > mh.sizeofcmds) {
408
// we passed the frame boundary of the load commands
409
pkg_emit_error("Mach-O structure misread.");
410
errno = EINVAL;
411
goto cleanup;
412
}
413
}
414
415
cleanup:
416
free(mf);
417
return ret;
418
}
419
420
int
421
pkg_analyse_init_macho(__unused const char *stage)
422
{
423
return EPKG_OK;
424
}
425
426
int
427
pkg_analyse_macho(const bool developer_mode, struct pkg *pkg,
428
const char *fpath, char **provided, enum pkg_shlib_flags *provided_flags)
429
{
430
assert(*provided == NULL);
431
assert(*provided_flags == PKG_SHLIB_FLAGS_NONE);
432
433
int ret = EPKG_OK;
434
pkg_debug(1, "Analysing Mach-O %s", fpath);
435
436
int fd = open(fpath, O_RDONLY);
437
if (-1 == fd) {
438
// pkg_emit_errno("open_pkg_analyse_macho", fpath);
439
// ret = EPKG_FATAL;
440
// Be consistent with analyse_elf and return no error if fpath cannot be opened
441
return ret;
442
} else {
443
ret = analyse_macho(fd, pkg, provided, provided_flags);
444
if (-1 == close(fd)) {
445
pkg_emit_errno("close_pkg_analyse_macho", fpath);
446
ret = EPKG_FATAL;
447
}
448
}
449
if (developer_mode) {
450
if (ret != EPKG_OK && ret != EPKG_END) {
451
return EPKG_WARN;
452
}
453
}
454
return ret;
455
}
456
457
int
458
pkg_analyse_close_macho()
459
{
460
return EPKG_OK;
461
}
462
463