Solved Linker issue with hello world C program?

cmoerz

Active Member

Reaction score: 51
Messages: 117

I believe I'm not seeing the forest for the trees and need some help. Can anybody tell me, why my incredibly simple C program fails to link?
Code:
#include <stdio.h>
#include <stdlib.h>

int main( int argc, char **argv ) {
    printf( "Hello, world!\n" );
    exit(0);
}

Makefile:
Code:
DEBUG=-g

.ifdef RELEASE
CCARGS=
.else
CCARGS=$(DEBUG) -c -Wall -pedantic -std=c99
.endif

LDARGS=-dynamic-linker --dy -L /usr/lib -L /lib -lc

CC=clang
LD=ld

all: main
        echo Compilation completed.

main.o: main.c
        $(CC) $(CCARGS) -o main.o main.c

main: main.o
        $(LD) $(LDARGS) -o main main.o

Compiling works with clang bug ld gives me
Code:
ld -dynamic-linker --dy -L /usr/lib -L /lib -lc -o main main.o
ld: error: /lib/libc.so.7: undefined reference to __progname
ld: error: /lib/libc.so.7: undefined reference to environ
*** Error code 1

Stop.
I don't get it. When I run clang -o test main.o I get an ELF executable that's also only linked to libc. So why the heck is ld failing?
Code:
% ldd test 
test:
        libc.so.7 => /lib/libc.so.7 (0x80024c000)
 

kpedersen

Son of Beastie

Reaction score: 2,153
Messages: 3,001

Code:
LDARGS=-dynamic-linker --dy -L /usr/lib -L /lib -lc

Why are your linker flags so unnecessarily complex? What are you trying to achieve?
 
OP
C

cmoerz

Active Member

Reaction score: 51
Messages: 117

Gaah, alright. Simply run clang with -v -v -v and you get all the options that I was missing. Duh:

Code:
FreeBSD clang version 10.0.1 (git@github.com:llvm/llvm-project.git llvmorg-10.0.1-0-gef32c611aa2)
Target: x86_64-unknown-freebsd12.2
Thread model: posix
InstalledDir: /usr/bin
 "/usr/bin/ld" --eh-frame-hdr -dynamic-linker /libexec/ld-elf.so.1 --hash-style=both --enable-new-dtags -o test /usr/lib/crt1.o /usr/lib/crti.o /usr/lib/crtbegin.o -L/usr/lib main.o -lgcc --as-needed -lgcc_s --no-as-needed -lc -lgcc --as-needed -lgcc_s --no-as-needed /usr/lib/crtend.o /usr/lib/crtn.o

To be honest. Those options were very simple (basically just a couple -L and -lc) in the beginning. It just didn't link. Now it appears it won't work without all those object files that clang pulled in as well.
 
OP
C

cmoerz

Active Member

Reaction score: 51
Messages: 117

Just for reference - this is, what I got without those bunch of additional arguments:
Code:
% ld -L/usr/lib -L/lib -lc main.o
ld: error: /lib/libc.so.7: undefined reference to __progname
ld: error: /lib/libc.so.7: undefined reference to environ

It works with the options above. So I'll flag this as resolved for now. Maybe someone has some reduced set of linker options, I'd be happy to go with a simpler Makefile. I don't feel like trial and error and will just use the clang provided ones for the moment.
 

kpedersen

Son of Beastie

Reaction score: 2,153
Messages: 3,001

Maybe someone has some reduced set of linker options, I'd be happy to go with a simpler Makefile. I don't feel like trial and error and will just use the clang provided ones for the moment.
Have you tried with:

Code:
LDARGS=
LD=clang

For such a simple program, you shouldn't don't actually need any linker arguments.
 

mark_j

Daemon

Reaction score: 736
Messages: 1,278

Yep, I'm with kpedersen in questioning what you're trying to achieve. --dynamic-linker should have a path to a linker. You're using a makefile for building a shared library by the looks of its origin. That certainly doesn't correlate with your successful cc command line build.
 
OP
C

cmoerz

Active Member

Reaction score: 51
Messages: 117

mark_j, you're right. That --dynamic-linker needs a parameter. It probably is superfluous; this option came from clang and uses linker /libexec/ld-elf.so.1 which - I suppose - is correct. Those flags didn't come from me.

Code:
"/usr/bin/ld" --eh-frame-hdr -dynamic-linker /libexec/ld-elf.so.1 --hash-style=both --enable-new-dtags -o test /usr/lib/crt1.o /usr/lib/crti.o /usr/lib/crtbegin.o -L/usr/lib main.o -lgcc --as-needed -lgcc_s --no-as-needed -lc -lgcc --as-needed -lgcc_s --no-as-needed /usr/lib/crtend.o /usr/lib/crtn.o

It's basically what kpedersen suggested - I ran clang (with -v -v) instead of ld, which then ran ld for me.

And yes, for a such a simple program, I shouldn't need to call ld separately; one can just as well do clang in one go. The idea of that exercise was to prepare for a more complicated setup down the road, though.

I haven't done any C programming for a while; last time I didn't need any crt object files to link - maybe that changed with clang. First time using clang for me, so thig might just come with the territory.
 

mark_j

Daemon

Reaction score: 736
Messages: 1,278

I thought the flags did come from you because you had it in the Makefile:
Code:
LDARGS=-dynamic-linker --dy -L /usr/lib -L /lib -lc

Sorry, I misunderstood.

Anyway, It should be -dynamic-linker.

IMO you're best to leave ld.lld(1) alone unless you're performing something non-standard, for example, relocatable shared objects, static linking etc.

Just let clang/cc do it for you, especially until you get your C-legs... 😎
 
OP
C

cmoerz

Active Member

Reaction score: 51
Messages: 117

Ah, right. Now I see it, too. Yup, that Makefile up there is wrong.

Also, I think the dynamic linker option requires a double dash. Maybe that was just ignored. Well, I said it before: forest for the trees...
 

kpedersen

Son of Beastie

Reaction score: 2,153
Messages: 3,001

That said, I recall something like the following working just fine on GCC:

Code:
$ gcc -c main.c
$ ld main.o -oprog

Whereas on clang, you need to pass in some flags* to ld in order to link a simple executable. Possibly because Clang is a cross compiler whereas individual builds of GCC aren't so naturally it is more complex because you need to tell it about the host.

*cc (clang) invokes it as:
"/usr/bin/ld" -e __start --eh-frame-hdr -Bdynamic -dynamic-linker /usr/libexec/ld.so -o a.out /usr/bin/../lib/crt0.o /usr/bin/../lib/crtbegin.o -L/usr/bin/../lib -L/usr/lib -L/usr/X11R6/lib -L/usr/local/lib main.o -lcompiler_rt -lc -lcompiler_rt /usr/bin/../lib/crtend.o
 

Geezer

Aspiring Daemon

Reaction score: 447
Messages: 761

I am coming in late on this thread.

For a hello world merely run:
cc hw.c (or whatever name you have given your C file)
./a.out

That's all you need. No errors.
 

mer

Aspiring Daemon

Reaction score: 394
Messages: 626

Sometimes an exercise like this also about gettng the Makefile correct. Next steps are typically "who do I compile a bunch of files in a subdirectory, put the output in a targeted directory based on machine characteristics and create a shared library from them that can be linked to other stuff. Oh and create install and clean targets for everything".

I'm not saying anything about the grammar corrections, except "They made me smile"
 

Paul Floyd

Active Member

Reaction score: 53
Messages: 138

Why are you making things unnecessarily complicated?

Just about all 'make' tools that I know of use some fairly standard make variables:
CPPFLAGS for the preprocessor, usually -D and -I options
CFLAGS flags for the C compiler, stuff like optimization/debug and warning flags
LDFLAGS for the linker

Unless you have a really good reason then I recommend that you stick to using these.

Last thing, I would also use the C compiler (or C++ compiler if you have any C++ code in your project) as the linker driver. I'm not on a FreeBSD box atm, but GNU make has the following builtin variables

Makefile:
LINK.cc = $(CXX) $(CXXFLAGS) $(CPPFLAGS) $(LDFLAGS) $(TARGET_ARCH)
LINK.c = $(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) $(TARGET_ARCH)

%: %.cc
#  recipe to execute (built-in):
        $(LINK.cc) $^ $(LOADLIBES) $(LDLIBS) -o $@

%: %.c
#  recipe to execute (built-in):
        $(LINK.c) $^ $(LOADLIBES) $(LDLIBS) -o $@
 
Top