Skip to content

Commit b47f2b1

Browse files
committed
Fix embedded LLD cross-compilation library discovery
The embedded LLD integration for cross-compilation (PR #4964) had several issues with library and CRT object discovery across different cross-toolchain layouts. Fixed by: 1. Expanding find_gcc_lib_dir() from 3 to 8 search base patterns to cover Debian gcc-cross packages (/usr/lib/gcc-cross/) and custom GCC installs (/usr/local/lib/gcc/), plus sysroot-relative paths. 2. Adding the GCC target lib directory (<prefix>/<triple>/lib/) as a library search path. GCC installs shared runtime libraries (libatomic, libgcc_s) there, separate from the version-specific directory where libgcc.a lives. 3. Conditionally passing --sysroot to LLD. Debian cross toolchains generate linker scripts with fully-qualified paths that already include the sysroot prefix, so --sysroot double-prepends (LLD doesn't fall back to the original path like GNU ld). Custom GCC sysroots use root-relative paths that need --sysroot. Detected by checking whether libc.so contains the sysroot path. 4. Making find_ponyc_crt_dir() architecture-aware by checking the ELF e_machine header of crtbeginS.o candidates. This prevents selecting native x86_64 CRT objects (from ponyc's exec dir) when cross-compiling for ARM or RISC-V. 5. Linking libponyrt by full path from the target-architecture CRT directory instead of -l search, which could resolve to the native-arch library via -L ordering. All three cross-compilation targets (arm, armhf, riscv64) verified locally against their CI container images.
1 parent ed605c9 commit b47f2b1

1 file changed

Lines changed: 150 additions & 20 deletions

File tree

src/libponyc/codegen/genexe.cc

Lines changed: 150 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -380,17 +380,67 @@ static const char* find_libc_crt_dir(const char* sysroot,
380380
return NULL;
381381
}
382382

383-
static const char* find_ponyc_crt_dir(ast_t* program, pass_opt_t* opt)
383+
static uint16_t expected_elf_machine(compile_t* c)
384384
{
385-
(void)opt;
385+
llvm::Triple triple(c->opt->triple);
386+
387+
switch(triple.getArch())
388+
{
389+
case llvm::Triple::x86_64: return 62; // EM_X86_64
390+
case llvm::Triple::aarch64: return 183; // EM_AARCH64
391+
case llvm::Triple::riscv64: return 243; // EM_RISCV
392+
case llvm::Triple::riscv32: return 243; // EM_RISCV
393+
case llvm::Triple::arm:
394+
case llvm::Triple::thumb: return 40; // EM_ARM
395+
default: return 0;
396+
}
397+
}
398+
399+
static bool elf_matches_target(const char* path, uint16_t target_machine)
400+
{
401+
// Read the ELF header and check e_machine matches the target.
402+
// If we can't determine the architecture, accept the file.
403+
if(target_machine == 0)
404+
return true;
405+
406+
FILE* f = fopen(path, "rb");
407+
if(f == NULL)
408+
return false;
409+
410+
unsigned char hdr[20];
411+
if(fread(hdr, 1, 20, f) < 20)
412+
{
413+
fclose(f);
414+
return false;
415+
}
416+
fclose(f);
417+
418+
// Verify ELF magic.
419+
if(hdr[0] != 0x7f || hdr[1] != 'E' || hdr[2] != 'L' || hdr[3] != 'F')
420+
return false;
421+
422+
// e_machine is at offset 18, 2 bytes, in the ELF header's native
423+
// byte order. hdr[5] == 1 means little-endian, 2 means big-endian.
424+
uint16_t machine;
425+
if(hdr[5] == 1)
426+
machine = (uint16_t)hdr[18] | ((uint16_t)hdr[19] << 8);
427+
else
428+
machine = ((uint16_t)hdr[18] << 8) | (uint16_t)hdr[19];
429+
430+
return machine == target_machine;
431+
}
432+
433+
static const char* find_ponyc_crt_dir(ast_t* program, compile_t* c)
434+
{
435+
uint16_t target_machine = expected_elf_machine(c);
386436
size_t count = program_lib_path_count(program);
387437
char buf[PATH_MAX];
388438

389439
for(size_t i = 0; i < count; i++)
390440
{
391441
const char* path = program_lib_path_at(program, i);
392442
snprintf(buf, sizeof(buf), "%s/crtbeginS.o", path);
393-
if(file_exists(buf))
443+
if(file_exists(buf) && elf_matches_target(buf, target_machine))
394444
return path;
395445
}
396446

@@ -402,27 +452,44 @@ static const char* find_gcc_lib_dir(const char* sysroot,
402452
{
403453
// Search for GCC runtime library directory containing libgcc.a.
404454
// Check multiple base paths and pick the highest version.
405-
const char* base_patterns[] = {
406-
"/usr/lib/gcc",
407-
NULL, // sysroot-relative patterns filled in below
408-
NULL
409-
};
455+
//
456+
// Debian/Ubuntu cross-compiler packages install to /usr/lib/gcc-cross/
457+
// rather than /usr/lib/gcc/. Custom GCC cross-compiler builds (e.g. the
458+
// arm/armhf CI containers) install to /usr/local/lib/gcc/.
459+
const char* base_patterns[8];
460+
int pattern_count = 0;
410461

411462
char sysroot_rel[PATH_MAX];
412-
char sysroot_internal[PATH_MAX];
463+
char sysroot_rel_cross[PATH_MAX];
464+
char sysroot_parent_rel[PATH_MAX];
465+
char sysroot_parent_rel_cross[PATH_MAX];
466+
467+
base_patterns[pattern_count++] = "/usr/lib/gcc";
468+
base_patterns[pattern_count++] = "/usr/lib/gcc-cross";
469+
base_patterns[pattern_count++] = "/usr/local/lib/gcc";
470+
base_patterns[pattern_count++] = "/usr/local/lib/gcc-cross";
413471

414-
// <sysroot>/../lib/gcc
472+
// <sysroot>/../lib/gcc (handles sysroot=/usr/<triple>)
415473
snprintf(sysroot_rel, sizeof(sysroot_rel), "%s/../lib/gcc", sysroot);
416-
base_patterns[1] = sysroot_rel;
474+
base_patterns[pattern_count++] = sysroot_rel;
417475

418-
// <sysroot>/lib/gcc
419-
snprintf(sysroot_internal, sizeof(sysroot_internal), "%s/lib/gcc", sysroot);
420-
base_patterns[2] = sysroot_internal;
476+
snprintf(sysroot_rel_cross, sizeof(sysroot_rel_cross),
477+
"%s/../lib/gcc-cross", sysroot);
478+
base_patterns[pattern_count++] = sysroot_rel_cross;
479+
480+
// <sysroot>/../../lib/gcc (handles sysroot=/usr/local/<triple>/libc)
481+
snprintf(sysroot_parent_rel, sizeof(sysroot_parent_rel),
482+
"%s/../../lib/gcc", sysroot);
483+
base_patterns[pattern_count++] = sysroot_parent_rel;
484+
485+
snprintf(sysroot_parent_rel_cross, sizeof(sysroot_parent_rel_cross),
486+
"%s/../../lib/gcc-cross", sysroot);
487+
base_patterns[pattern_count++] = sysroot_parent_rel_cross;
421488

422489
const char* best_dir = NULL;
423490
int best_version = -1;
424491

425-
for(int p = 0; p < 3; p++)
492+
for(int p = 0; p < pattern_count; p++)
426493
{
427494
char triple_dir[PATH_MAX];
428495
snprintf(triple_dir, sizeof(triple_dir), "%s/%s",
@@ -561,7 +628,7 @@ static bool link_exe_lld_elf(compile_t* c, ast_t* program,
561628
return false;
562629
}
563630

564-
const char* ponyc_crt_dir = find_ponyc_crt_dir(program, c->opt);
631+
const char* ponyc_crt_dir = find_ponyc_crt_dir(program, c);
565632
if(ponyc_crt_dir == NULL)
566633
{
567634
errorf(errors, NULL,
@@ -598,6 +665,40 @@ static bool link_exe_lld_elf(compile_t* c, ast_t* program,
598665
args.push_back("--build-id");
599666
args.push_back("--eh-frame-hdr");
600667

668+
// Pass --sysroot so LLD can resolve absolute paths in linker scripts
669+
// (e.g. libc.so referencing /lib/libc.so.6). However, some cross
670+
// toolchains (Debian gcc-cross packages) generate linker scripts with
671+
// fully-qualified paths that already include the sysroot prefix. LLD
672+
// doesn't fall back to the original path after prepending sysroot
673+
// (unlike GNU ld), so --sysroot would cause double-prepending. Detect
674+
// this by checking whether libc.so contains the sysroot path.
675+
{
676+
bool needs_sysroot = true;
677+
char libc_so_path[PATH_MAX];
678+
snprintf(libc_so_path, sizeof(libc_so_path), "%s/libc.so", libc_crt_dir);
679+
680+
FILE* f = fopen(libc_so_path, "r");
681+
if(f != NULL)
682+
{
683+
char line[1024];
684+
while(fgets(line, sizeof(line), f) != NULL)
685+
{
686+
if(strstr(line, sysroot) != NULL)
687+
{
688+
needs_sysroot = false;
689+
break;
690+
}
691+
}
692+
fclose(f);
693+
}
694+
695+
if(needs_sysroot)
696+
{
697+
snprintf(buf, sizeof(buf), "--sysroot=%s", sysroot);
698+
args.push_back(stringtab(buf));
699+
}
700+
}
701+
601702
if(c->opt->staticbin)
602703
{
603704
args.push_back("-static");
@@ -659,6 +760,22 @@ static bool link_exe_lld_elf(compile_t* c, ast_t* program,
659760
{
660761
snprintf(buf, sizeof(buf), "-L%s", gcc_lib_dir);
661762
args.push_back(stringtab(buf));
763+
764+
// GCC installs shared runtime libraries (libatomic, libgcc_s) in
765+
// <prefix>/<triple>/lib/ while static libraries (libgcc.a) go in
766+
// <prefix>/lib/gcc[-cross]/<triple>/<version>/. Derive the former
767+
// from the latter.
768+
char gcc_target_lib[PATH_MAX];
769+
snprintf(gcc_target_lib, sizeof(gcc_target_lib),
770+
"%s/../../../../%s/lib", gcc_lib_dir, sys_triple);
771+
772+
struct stat gcc_target_stat;
773+
if(stat(gcc_target_lib, &gcc_target_stat) == 0
774+
&& S_ISDIR(gcc_target_stat.st_mode))
775+
{
776+
snprintf(buf, sizeof(buf), "-L%s", gcc_target_lib);
777+
args.push_back(stringtab(buf));
778+
}
662779
}
663780

664781
// Add ponyc lib paths and user lib paths.
@@ -688,13 +805,26 @@ static bool link_exe_lld_elf(compile_t* c, ast_t* program,
688805
}
689806
}
690807

691-
// Pony runtime.
808+
// Pony runtime. Use the full path from ponyc_crt_dir to ensure we
809+
// pick the target-architecture library, not a native-arch copy that
810+
// might appear earlier in the -L search path.
692811
if(!c->opt->runtimebc)
693812
{
694-
if(c->opt->staticbin)
695-
args.push_back("-lponyrt");
813+
const char* rt_name = c->opt->staticbin ? "libponyrt.a"
814+
: (c->opt->pic ? "libponyrt-pic.a" : "libponyrt.a");
815+
816+
snprintf(buf, sizeof(buf), "%s/%s", ponyc_crt_dir, rt_name);
817+
if(file_exists(buf))
818+
{
819+
args.push_back(stringtab(buf));
820+
}
696821
else
697-
args.push_back(c->opt->pic ? "-lponyrt-pic" : "-lponyrt");
822+
{
823+
if(c->opt->staticbin)
824+
args.push_back("-lponyrt");
825+
else
826+
args.push_back(c->opt->pic ? "-lponyrt-pic" : "-lponyrt");
827+
}
698828
}
699829

700830
args.push_back("-lpthread");

0 commit comments

Comments
 (0)