Skip to content

Stage2 ARM Thumb Debug: LLD fails with "undefined symbol: __clzsi2" #13465

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
bcrist opened this issue Nov 6, 2022 · 4 comments
Closed

Stage2 ARM Thumb Debug: LLD fails with "undefined symbol: __clzsi2" #13465

bcrist opened this issue Nov 6, 2022 · 4 comments
Labels
bug Observed behavior contradicts documented or intended behavior

Comments

@bcrist
Copy link
Contributor

bcrist commented Nov 6, 2022

Zig Version

0.10.0

Steps to Reproduce and Observed Behavior

When compiling embedded firmware targeting ARM Cortex M0+, using the stage2 compiler, in debug mode, the linker fails with this message:

LLD Link... ld.lld: error: undefined symbol: __clzsi2
>>> referenced by compiler_rt
>>>               C:\...\zig\o\abc2490849d1b9cc45a64ab5470e2f9d\libcompiler_rt.a.o:(__udivsi3) in archive C:\...\zig\o\abc2490849d1b9cc45a64ab5470e2f9d\libcompiler_rt.a
>>> referenced by compiler_rt
>>>               C:\...\zig\o\abc2490849d1b9cc45a64ab5470e2f9d\libcompiler_rt.a.o:(__udivsi3) in archive C:\...\zig\o\abc2490849d1b9cc45a64ab5470e2f9d\libcompiler_rt.a
>>> referenced by compiler_rt
>>>               C:\...\zig\o\abc2490849d1b9cc45a64ab5470e2f9d\libcompiler_rt.a.o:(__umodsi3) in archive C:\...\zig\o\abc2490849d1b9cc45a64ab5470e2f9d\libcompiler_rt.a
>>> referenced 1 more times
>>> did you mean: __ctzsi2
>>> defined in: C:\...\zig\o\abc2490849d1b9cc45a64ab5470e2f9d\libcompiler_rt.a(C:\...\zig\o\abc2490849d1b9cc45a64ab5470e2f9d\libcompiler_rt.a.o)

This doesn't happen when using any of the -Drelease-* modes, or when using the stage1 compiler. The reason it doesn't happen in release modes may just be that all the integer division and modulo operations are optimized out. I'm not exactly sure where those are coming from, even in debug; the only division I can think of should be comptime only.

It looks like lib/compiler_rt/count0bits.zig does have an implementation of __clzsi2 specifically for Thumb targets, but somehow the linker doesn't see it.

The specific CrossTarget being used is:

std.zig.CrossTarget{
    .cpu_arch = .thumb,
    .cpu_model = .{ .explicit = &std.Target.arm.cpu.cortex_m0plus },
    .os_tag = .freestanding,
    .abi = .none,
},

It can be reproduced by running zig build after checking out this commit: https://github.com/bcrist/microbe/tree/0b7404760350ef3504d95eb1200d6900c8b2a374

Running zig build again after the failure doesn't print the error again, it just triggers a FileNotFound error, but I think that's caused by a different issue: #13432

Deleting the zig-cache directory and building again shows the linker error again.

Expected Behavior

The linker should succeed and generate a valid ELF binary.

@bcrist bcrist added the bug Observed behavior contradicts documented or intended behavior label Nov 6, 2022
@bcrist
Copy link
Contributor Author

bcrist commented Nov 6, 2022

After a little further poking, I wondered if this might be related to lib/compiler_rt/count0bits.zig declaring const __clzsi2 = switch(...) ... where sometimes, it resolves to fn (i32) callconv(.C) i32, but other times to fn (i32) callconv(.Naked) i32. I copied the switch statement into the comptime block at the top and changed it to export the __clzsi2_thumb1 and __clzsi2_arm32 functions directly, when necessary:

    switch (builtin.cpu.arch) {
        .arm, .armeb, .thumb, .thumbeb => {
            const use_thumb1 =
                (builtin.cpu.arch.isThumb() or
                std.Target.arm.featureSetHas(builtin.cpu.features, .noarm)) and
                !std.Target.arm.featureSetHas(builtin.cpu.features, .thumb2);

            if (use_thumb1) {
                @export(__clzsi2_thumb1, .{ .name = "__clzsi2", .linkage = common.linkage });
            }
            // From here on we're either targeting Thumb2 or ARM.
            else if (!builtin.cpu.arch.isThumb()) {
                @export(__clzsi2_arm32, .{ .name = "__clzsi2", .linkage = common.linkage });
            }
            // Use the generic implementation otherwise.
            else {
                @export(clzsi2_generic, .{ .name = "__clzsi2", .linkage = common.linkage });
            }
        },
        else => @export(clzsi2_generic, .{ .name = "__clzsi2", .linkage = common.linkage }),
    }

Obviously, this isn't very clean, but I am able to successfully link a working executable in debug mode with this change. I found the new libcompiler_rt.a.o and threw it into ghidra to verify that it contains the thumb1 version of __clzsi2 that uses a LUT. The old version just contained an extern thunk.

I don't know exactly what this means in terms of the root cause of this bug, but hopefully this helps.

On a side note, does anyone know why CPUs marked with the .thumb2 feature are excluded from using __clzsi2_thumb1? My understanding was that Thumb2 was a superset of Thumb1 -- technically the STM32G0 chip I'm using is a Thumb2 core, Edit: apparently it's not Thumb2, just Thumb1 with "Thumb-2 Technology"

@Vexu
Copy link
Member

Vexu commented Dec 5, 2022

Based on your follow up I think this is caused by #13465

@Vexu Vexu closed this as completed Dec 5, 2022
@nektro
Copy link
Contributor

nektro commented Dec 5, 2022

that's the same number as this one?

@Vexu
Copy link
Member

Vexu commented Dec 5, 2022

Meant #13706

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Observed behavior contradicts documented or intended behavior
Projects
None yet
Development

No branches or pull requests

3 participants