0

I am currently learning assembly. I write simple test programs so I put together a simple makefile :

AS = nasm                                                                       
CC = ld                                                                         
                                                                                
ASFLAGS = -f elf64                                                              
                                                                                
all:    hello_name put_digit                                                    
                                                                                
hello_name: hello_name.o                                                        
                                                                                
put_digit:  put_digit.o                                                         
                                                                                
clean:                                                                          
            ${RM} *.o                                                           
                                                                                
fclean:     clean                                                               
            ${RM} hello_name put_digit                                          
                                                                                
.PHONY: all clean fclean

I have always thought that implicit rules were not really useful since you end up rewriting the rules in any substantial project. But trying to put up a makefile quickly when experimenting seems like a good use case for them instead of writing a build script. So here I am using them.

How frustrating it is to discover that there is not a LD variable so I have to hack CC to get ld. Implicit rules also do not take into account .asm files so I have to disguise them as .s files although I am using the intel/nasm syntax.

Is there any good/historical reason for this or are implicit rules a half-baked thing ?

cassepipe
  • 371
  • 6
  • 16
  • 1
    The implicit rules are given by POSIX and have “organically grown.” Just define your own rules if you need them. Linking through the C compiler is usually what you want to get the C runtime initialisation code. – fuz May 07 '21 at 09:47
  • 2
    This makefile seems like more trouble than it's worth :/ For single-file asm messing around, I just use a nasm + ld script I wrote (included in [this answer](https://stackoverflow.com/questions/36861903/assembling-32-bit-binaries-on-a-64-bit-system-gnu-toolchain/36901649#36901649)) that runs `nasm -felf64 $1 && ld -o basename basename.o` (or elf32 with -m32), with a few other options like for adding a library. Or a one-off command line like `nasm -felf64 foo.asm && gcc foo.o` to assemble + link one with a main. Up-arrow makes it just as fast to recall that as a `make`. Or control-r nasm – Peter Cordes May 07 '21 at 09:53
  • 2
    Implicit rules are there to support the huge majority of users who want to do simple straightforward things, on POSIX-ish systems with POSIX-ish conventions. People who want to do unusual things (invoking the linker directly is unusual: as @raspy says most people want/need the compiler front-end to link) or have unusual conventions (POSIX compilers use `.s` and `.S` for assembler input) need to define their own rules. Yes, the rules could have made `LD = $(CC)` by default and used `$(LD)` but... they didn't... – MadScientist May 07 '21 at 14:09

1 Answers1

2

There is a built-in and defined LD variable, only it is not used in this implicit rule. The rule itself is:

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

with LINK.o being defined as

# default
LINK.o = $(CC) $(LDFLAGS) $(TARGET_ARCH)

I believe this is a correct way to go. The compiler driver should be invoked to run the linker as in general (unless you use a custom linking script) it also includes some of compiler-provided startup files, which otherwise you would need to specify on your own and this is compiler-specific.

Consider this simple example:

$ cat hello.c
int main(int argc, char *argv[]) {
  return 0;
}

One might think this is a complete program, but it is not. The platform that it is compiled requires some additional preparation. In fact, linker on its own will warn:

$ gcc -c hello.c
$ ld -o hello hello.o
ld: warning: cannot find entry symbol _start; defaulting to 0000000000401000
$ ./hello
Segmentation fault

When the compiler driver is used, it feeds the linker with a bunch of options and startup files:

$ gcc -Wl,-v -o hello hello.o
collect2 version 9.3.0
/usr/bin/ld -plugin /usr/lib/gcc/x86_64-linux-gnu/9/liblto_plugin.so -plugin-opt=/usr/lib/gcc/x86_64-linux-gnu/9/lto-wrapper -plugin-opt=-fresolution=/tmp/ccRdkBP1.res -plugin-opt=-pass-through=-lgcc -plugin-opt=-pass-through=-lgcc_s -plugin-opt=-pass-through=-lc -plugin-opt=-pass-through=-lgcc -plugin-opt=-pass-through=-lgcc_s --build-id --eh-frame-hdr -m elf_x86_64 --hash-style=gnu --as-needed -dynamic-linker /lib64/ld-linux-x86-64.so.2 -pie -z now -z relro -o hello /usr/lib/gcc/x86_64-linux-gnu/9/../../../x86_64-linux-gnu/Scrt1.o /usr/lib/gcc/x86_64-linux-gnu/9/../../../x86_64-linux-gnu/crti.o /usr/lib/gcc/x86_64-linux-gnu/9/crtbeginS.o -L/usr/lib/gcc/x86_64-linux-gnu/9 -L/usr/lib/gcc/x86_64-linux-gnu/9/../../../x86_64-linux-gnu -L/usr/lib/gcc/x86_64-linux-gnu/9/../../../../lib -L/lib/x86_64-linux-gnu -L/lib/../lib -L/usr/lib/x86_64-linux-gnu -L/usr/lib/../lib -L/usr/lib/gcc/x86_64-linux-gnu/9/../../.. -v hello.o -lgcc --push-state --as-needed -lgcc_s --pop-state -lc -lgcc --push-state --as-needed -lgcc_s --pop-state /usr/lib/gcc/x86_64-linux-gnu/9/crtendS.o /usr/lib/gcc/x86_64-linux-gnu/9/../../../x86_64-linux-gnu/crtn.o
GNU ld (GNU Binutils for Ubuntu) 2.34
$ ./hello
$ echo $?
0

So the final hello binary is linked from Scrt1.o, crti.o, crtbeginS.o, hello.o, crtendS.o, crtn.o, most of which were provided by the compiler. These were required for the binary to work.

Your specific example uses different tools, which may not be compatible with this approach. This does not make implicit rules "half-baked" and you are always welcome to define linking rule on your own to match the toolset used.

raspy
  • 3,995
  • 1
  • 14
  • 18
  • Thanks for the the thourough reply to my somewhat rant-ish question. I was indeed trying to define my own `_start` function and thus not interested in `gcc`'s batteries-included linking. I now understand this workflow is alas just not the one catered by `GNU Make` and will use the (not so simple) build script from @PeterCordes 's great answer : https://stackoverflow.com/questions/36861903/assembling-32-bit-binaries-on-a-64-bit-system-gnu-toolchain/36901649#36901649 – cassepipe May 07 '21 at 14:59