Running gcc's steps manually, compiling, assembling, linking - c

If you have a simple C program, like
int main(void) {return 0;}
It can be compiled with gcc -o test test.c.
As I understand, gcc performs compiling, assembling then linking. The latter two steps are achieved by it running as and ld.
I can generate the assembly code by using gcc -S test.c.
What would you type into a terminal, to convert the assembly code into an executable?
(the reason for doing so is to learn assembly)

These are the different stages using gcc
gcc -E --> Preprocessor, but don't compile
gcc -S --> Compile but don't assemble
gcc -c --> Preprocess, compile, and assemble, but don't link
gcc with no switch will link your object files and generate the executable

// main.c
#include <stdio.h>
int main(void)
{
printf("Hello World !\n");
return 0;
}
For preprocessing, compiling, assembling and then finally linking the simple aforementioned hello world program, follow the steps below:
Step 1/4) Preprocess main.c to generate main.i:
$: gcc -E main.c -o main.i
NOTE: You could call the C preprocessor directly as well:
$: cpp main.c -o main.i
Step 2/4) Compile main.i to generate main.s:
$: gcc -S main.i -o main.s
Step 3/4) Assemble main.s to generate main.o:
$: as main.s -o main.o
NOTE: You can combine the aforementioned steps 1, 2 and 3 by using the -c (small C) flag of gcc:
$: gcc -c main.s -o main.o // OR $: gcc -c main.c -o main.o
Step 4/4) Link main.o with other necessary object files namely, crti.o & crtn.o (they define function prologs & epilogs, respectively), crt1.o (contains _start symbol for bootstrapping the initial execution of the program), libc.so path or -lc flag for libc and then finally set the name of the dynamic linker, to generate a dynamically linked ELF executable:
On x86_64:
$: ld /usr/lib/x86_64-linux-gnu/crti.o /usr/lib/x86_64-linux-gnu/crtn.o /usr/lib/x86_64-linux-gnu/crt1.o -lc main.o -dynamic-linker /lib64/ld-linux-x86-64.so.2 -o main_ELF_executable
OR (if you'd like to specify path to libc.so)
$: ld /usr/lib/x86_64-linux-gnu/crti.o /usr/lib/x86_64-linux-gnu/crtn.o /usr/lib/x86_64-linux-gnu/crt1.o /usr/lib/x86_64-linux-gnu/libc.so main.o -dynamic-linker /lib64/ld-linux-x86-64.so.2 -o main_ELF_executable
On 32-bit ARM:
$: ld /usr/lib/arm-linux-gnueabihf/crti.o /usr/lib/arm-linux-gnueabihf/crtn.o /usr/lib/arm-linux-gnueabihf/crt1.o -lc main.o -dynamic-linker /lib/ld-linux.so.3 -o main_ELF_executable
OR (if you'd like to specify path to libc.so)
$: ld /usr/lib/arm-linux-gnueabihf/crti.o /usr/lib/arm-linux-gnueabihf/crtn.o /usr/lib/arm-linux-gnueabihf/crt1.o /usr/lib/arm-linux-gnueabihf/libc.so main.o -dynamic-linker /lib/ld-linux-armhf.so.3 -o main_ELF_executable
You can then run the ELF executable 'main_ELF_executable':
$: ./main_ELF_executable
Hello World !
Sources:
https://linux.die.net/man/1/gcc
https://linux.die.net/man/1/ld
https://dev.gentoo.org/~vapier/crt.txt

gcc test.s -o test will compile the test from test.s for you.
NASM might also be worth your time -- it might be easier / more friendly than gcc for compiling assembly.

After you do gcc -S -o test.s test.c, type gcc -o test test.s.

As you may or may not know, the four stages of compilation are to preprocess (-E), compile to assembly (-S), assemble to object code (-c), and finally link. The hardest for me to figure out was how to use the preprocessor output. Here's how to do it:
gcc -E hello.c | gcc -S -xc -o hello.s -
gcc -c hello.s -o hello.o
gcc hello.o -o hello

You can have gcc start and stop the compilation process wherever you want. gcc test.s -o test will have it compile test.s from assembly into an executable.

what I did was first I run the preprocessor by
clang++ test.cpp -E > test.i
then compiled it with ...
clang++ -S test.i
it should create a assembly file test.s ... then make the machine insturction file by
as test.s -o test.o
now you need to link it which is kinda confusing for dumb peoples like me ...
so we don't know the arguments for our last process which is linking ... to find out ... run
clang++ -v test.s
it should give you some big text of something ... find this line "-dynamic-linker" ... there's definately a -dynamic-linker in your output text ... now copy the text from -dynamic-linker to rest of the output ... just copy everything afterwards including "-dynamic-linker" ... now what i got is ...
-dynamic-linker /system/bin/linker -o a.out /data/data/com.termux/files/usr/lib/gcc/arm-linux-androideabi/11.1.0/../../../../lib/crtbegin_dynamic.o -L/data/data/com.termux/files/usr/lib/gcc/arm-linux-androideabi/11.1.0 -L/data/data/com.termux/files/usr/lib/gcc/arm-linux-androideabi/11.1.0/../../../../lib -L/data/data/com.termux/files/usr/lib/gcc/arm-linux-androideabi/11.1.0/../../.. -L/data/data/com.termux/files/usr/lib -L/system/lib /data/data/com.termux/files/usr/tmp/test-169b42.o -lc++_shared -lgcc -ldl -lm -lc -lgcc -ldl /data/data/com.termux/files/usr/lib/gcc/arm-linux-androideabi/11.1.0/../../../../lib/crtend_android.o
in this what you have to change is where your object file is ... in my case it is /data/data/com.termux/files/usr/tmp/test-169b42.o ... i need to change it to where my test.o file is ... /data/data/com.termux/files/home/CPP/Cpp_Log/hello_world/test.o ... this is where my test.o file is ...
so the argument we have to pass is ...
-dynamic-linker /system/bin/linker -o a.out /data/data/com.termux/files/usr/lib/gcc/arm-linux-androideabi/11.1.0/../../../../lib/crtbegin_dynamic.o -L/data/data/com.termux/files/usr/lib/gcc/arm-linux-androideabi/11.1.0 -L/data/data/com.termux/files/usr/lib/gcc/arm-linux-androideabi/11.1.0/../../../../lib -L/data/data/com.termux/files/usr/lib/gcc/arm-linux-androideabi/11.1.0/../../.. -L/data/data/com.termux/files/usr/lib -L/system/lib /data/data/com.termux/files/home/CPP/Cpp_Log/hello_world/main.o -lc++_shared -lgcc -ldl -lm -lc -lgcc -ldl /data/data/com.termux/files/usr/lib/gcc/arm-linux-androideabi/11.1.0/../../../../lib/crtend_android.o
now to link ... use the ld ... so the command is ld args -o test or in our case ...
ld -dynamic-linker /system/bin/linker -o a.out /data/data/com.termux/files/usr/lib/gcc/arm-linux-androideabi/11.1.0/../../../../lib/crtbegin_dynamic.o -L/data/data/com.termux/files/usr/lib/gcc/arm-linux-androideabi/11.1.0 -L/data/data/com.termux/files/usr/lib/gcc/arm-linux-androideabi/11.1.0/../../../../lib -L/data/data/com.termux/files/usr/lib/gcc/arm-linux-androideabi/11.1.0/../../.. -L/data/data/com.termux/files/usr/lib -L/system/lib /data/data/com.termux/files/home/CPP/Cpp_Log/hello_world/main.o -lc++_shared -lgcc -ldl -lm -lc -lgcc -ldl /data/data/com.termux/files/usr/lib/gcc/arm-linux-androideabi/11.1.0/../../../../lib/crtend_android.o -pie -o test ...
since andriod 5+ can only run pie elf executables ... i also added the "-pie" (position independent executable) before the -o test ...
now it should give you a executable file test ... just run it by ./test
it should work

Related

How to use ld to link compiled .o files [duplicate]

I'm trying to get my head around how the linking process works when producing an executable. To do that I'm reading Ian Taylor's blog series about it, but a lot of it is beyond me at the moment - so I'd like to see how it works in practice.
At the moment I produce some object files and link them via gcc with:
gcc -m32 -o test.o -c test.c
gcc -m32 -o main.o -c main.c
gcc -m32 -o test main.o test.o
How do I replicate the gcc -m32 -o test main.o test.o stage using ld?
I've tried a very naive: ld -A i386 ./test.o ./main.o
But that returns me these errors:
ld: i386 architecture of input file `./test.o' is incompatible with i386:x86-64 output
ld: i386 architecture of input file `./main.o' is incompatible with i386:x86-64 output
ld: warning: cannot find entry symbol _start; defaulting to 00000000004000b0
./test.o: In function `print_hello':
test.c:(.text+0xd): undefined reference to `_GLOBAL_OFFSET_TABLE_'
test.c:(.text+0x1e): undefined reference to `puts'
./main.o: In function `main':
main.c:(.text+0x15): undefined reference to `_GLOBAL_OFFSET_TABLE_
I'm most confused by _start and _GLOBAL_OFFSET_TABLE_ being missing - what additional info does gcc give to ld to add them?
Here are the files:
main.c
#include "test.h"
void main()
{
print_hello();
}
test.h
void print_hello();
test.c
#include <stdio.h>
void print_hello()
{
puts("Hello, world");
}
#sam : I am not the best people to answer your question because I am a beginner in compilation. I know how to compile programs but I do not really understand all the details (https://en.wikipedia.org/wiki/Compilers:_Principles,_Techniques,_and_Tools)
So, I decided this year to try to understand how compilation works and I tried to do, more or less, the same things as you tried a few days ago. As nobody has answered, I am going to expose what I have done but I hope an expert will supplement my answer.
Short answer : It is recommended to not use ld directly but to use gcc directly instead. Nevertheless, it is, as you write, interesting to know how the linking process works. This command works on my computer :
ld -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o test test.o main.o /usr/lib/crt1.o /usr/lib/libc.so /usr/lib/crti.o /usr/lib/crtn.o
Very Long answer :
How did I find the command above ?
As n.m suggested, run gcc with -v option.
gcc -v -m32 -o test main.o test.o
... /usr/libexec/gcc/x86_64-redhat-linux/4.8.5/collect2 ... (many
options and parameters)....
If you run ld with these options and parameters (copy and paste), it should work.
Try your command with -m elf_i386 (cf. collect2 parameters)
ld -m elf_i386 test.o main.o
ld: warning: cannot find entry symbol _start; ....
Look for symbol _start in object files used in the full ld command.
readelf -s /usr/lib/crt1.o (or objdump -t)
Symbol table '.symtab' contains 18 entries: Num: Value Size
Type Bind Vis Ndx Name... 11: 00000000 0 FUNC
GLOBAL DEFAULT 2 _start
Add this object to your ld command :ld -m elf_i386 test.o main.o /usr/lib/crt1.o
... undefined reference to `__libc_csu_fini'...
Look for this new reference in object files. It is not so obvious to know which library/object files are used because of -L, -l options and some .so include other libraries. For example, cat /usr/lib/libc.so. But, ld with --trace option helps. Try this commandld --trace ... (collect2 parameters)At the end, you should findld -m elf_i386 -o test test.o main.o /usr/lib/crt1.o /usr/lib/libc_nonshared.a /lib/libc.so.6 /usr/lib/crti.oor shorter (cf. cat /usr/lib/libc.so) ld -m elf_i386 -o test test.o main.o /usr/lib/crt1.o /usr/lib/libc.so /usr/lib/crti.o
It compiles but it does not run (Try to run ./test). It needs the right -dynamic-linker option because it is a dynamically linked ELF executable. (cf collect2 parameters to find it) ld -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o test test.o main.o /usr/lib/crt1.o /usr/lib/libc.so /usr/lib/crti.o But, it does not run (Segmentation fault (core dumped)) because you need the epilogue of the _init and _fini functions (https://gcc.gnu.org/onlinedocs/gccint/Initialization.html). Add the ctrn.o object. ld -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o test test.o main.o /usr/lib/crt1.o /usr/lib/libc.so /usr/lib/crti.o /usr/lib/crtn.o./test
Hello, world

Fail to create an executable file linking a static library

In folder test, I create hello.h,hello.c,main.c.
My goal is to create a static lib from hello.h, hello.c and an executable file from the library and main.c.
The following is what I have done.
hello.h:
#ifndef HELLO_H
#define HELLO_H
void hello(const char* name);
#endif
hello.c:
#include <stdio.h>
void hello(const char* name){
printf("hello %s! \n",name);
}
main.c:
#include "hello.h"
int main(){
hello("everyone");
return 0;
}
In the terminal (in the test folder): I run
gcc -c hello.c
ar crv libmyhello.a hello.o // to create a staticlib
gcc -c main.c
ld -o Cuteee hello.o -lmyhello
>>> ld: cannot find -lmyhello
I wonder if anything is wrong?
You need to provide -L to let gcc know where to look for your -l libraries:
gcc -c hello.c
ar crv libmyhello.a hello.o
gcc -c main.c
gcc main.o -L. -lmyhello -o Cuteee
To create the final executable it's enough to use gcc, ld is not needed.
See this question in order to understand why you probably don't need to use ld specifically.
This takes account of your comments:
Then I tried ld -o Cuteee main.o -L. -lmyhello but still fails with ld: warning: cannot find entry symbol _start; defaulting to 00000000004000b0 ./libmyhello.a(hello.o): In function 'hello': hello.c:(.text+0x1e): undefined reference to 'printf' I am puzzled again.
gcc is the GCC tooldriver for compiling and linking C programs.
When you invoke it with options and inputs that signify you want to a compile a C
source file, say hello.c, it first invokes the GNU C compiler, cc1, to compile hello.c
file to a temporary assembly file, say /tmp/cc8bfSqS.s. It quietly adds to the compiler commandline
various boilerplate options that are invariant for compiling C
on your system, to spare you the trouble.
Then it invokes the GNU assembler, as, to assemble /tmp/cc8bfSqS.s to the object file
hello.o.
You can pick out all of this from the compilation output if you ask gcc to be verbose,
e.g.
gcc -v -c hello.c
When you invoke gcc with options and inputs that signify you want to link
object files and possibly libraries into a program or shared library,
it invokes the GCC internal tool collect2 to do it - which
in turn invokes the system linker ld - and gcc quietly adds to the commandline
many boilerplate options, libraries and object files that are always
required for linking a C language program or shared library, once again to
spare you trouble.
You have used gcc to compile hello.c and main.c and allowed it to Do The Right
Thing behind the scenes. You haven't attempted to invoke cc1 and as yourself.
But in contrast, when you come to link your program, you haven't used gcc; you've
invoked ld yourself, without any of the boilerplate additions to the
the commandline that gcc would make. That's why the linkage fails.
If you link your program with gcc in verbose mode:
gcc -v -o Cuteee main.o -L. -lhello
you can pick the collect2 commandline out of the output, something like:
/usr/lib/gcc/x86_64-linux-gnu/7/collect2 \
-plugin /usr/lib/gcc/x86_64-linux-gnu/7/liblto_plugin.so \
-plugin-opt=/usr/lib/gcc/x86_64-linux-gnu/7/lto-wrapper \
-plugin-opt=-fresolution=/tmp/ccgWPdno.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 \
--sysroot=/ --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 Cuteee \
/usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu/Scrt1.o \
/usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu/crti.o \
/usr/lib/gcc/x86_64-linux-gnu/7/crtbeginS.o -L. \
-L/usr/lib/gcc/x86_64-linux-gnu/7 \
-L/usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu \
-L/usr/lib/gcc/x86_64-linux-gnu/7/../../../../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/7/../../.. \
main.o -lhello -lgcc --as-needed -lgcc_s --no-as-needed -lc -lgcc \
--as-needed -lgcc_s --no-as-needed \
/usr/lib/gcc/x86_64-linux-gnu/7/crtendS.o \
/usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu/crtn.o
All of those options that are passed to collect2 are passed through to
ld. So if you replace /usr/lib/gcc/x86_64-linux-gnu/7/collect2 with
ld in that monster commandline (or rather the commandline you get on
your own system), you will find that it links your program ./Cuteee.
That is what linking the program with gcc does, over an above:
ld -o Cuteee hello.o -lmyhello
One of the errors that your linkage attempt fails on:
cannot find entry symbol _start
is due to the fact that you haven't linked Scrt1.o (/usr/lib/x86_64-linux-gnu/Scrt1.o,
in the commandline above) which contains the C runtime initialization code for
a dynamically linked C program: it defines the symbol _start, whose address is the entry point of the program,
to which the loader passes initial control at runtime, and after program initialization
is complete it calls main.
The other linkage error:
undefined reference to 'printf
is due to the fact that you haven't linked the standard C library, -lc
(/lib/x86_64-linux-gnu/libc.so.6).
Programmers don't link with ld directly if they don't have to - e.g.
unless they're targeting an application to a bare-metal environment, and you
can see why.
the following proposed code:
corrects several problems in the posted code and in the command line statements.
performs the desired operation
cleanly compiles/links
And now the proposed changes to the posted code and command line statements:
hello.h
#ifndef HELLO_H
#define HELLO_H
void hello( const char* );
#endif
=======================
hello.c:
#include <stdio.h>
#include "hello.h"
void hello(const char* name)
{
printf("hello %s! \n",name);
}
========================
main.c:
#include "hello.h"
int main( void )
{
hello("everyone");
return 0;
}
=========================
In terminal (in test folder):
gcc -Wall -Wextra -Wconversion -pedantic -std=gnu11 -c hello.c -o hello.o -I.
ar crv libmyhello.a hello.o
=========================
gcc -Wall -Wextra -Wconversion -pedantic -std=gnu11 -c main.c -o main.o -I.
ld -static main.o -o Cuteee -L. -lmyhello
=========================
./Cuteee
=========================
this should eliminate the error message:
>>> ld: cannot find -lmyhello

How to link a simple program in Windows with Mingw GCC

I have a simple "Hello World!" c program, named hello.c on my desktop:
#include <stdio.h>
int main() {
printf("Hello world!\n");
return 0;
}
I run the following commands.
I pre-process it with : cpp hello.c > hello.i
I compile it with : gcc -S hello.i
I assemble it with : as -o hello.o hello.s
All good so far. But, i'm unable to link it. I've tried, among other commands, these:
ld -o hello.exe hello.o
ld -o hello.exe hello.o -lgcc
ld -o hello.exe hello.o -nostdlib -lgcc
Nothing works. The link errors i get in every single case are :
hello.o:hello.c:(.text+0x9): undefined reference to `__main'
hello.o:hello.c:(.text+0x15): undefined reference to `puts'
How can i link this assembled program hello.o in order to finally produce the executable program hello.exe? What am i missing? [Using Windows 8.1, Mingw version 0.6.2.] Thanks in advance.
Even if your answers to clarification questions are not particularly useful:
Try something like
ld hello.o -lmsvcrt -entry=_main -subsystem=console -o hello.exe
If you want to see the linker command line the standard gcc uses, invoke gcc like so:
gcc test.c -o test -Wl,-v
The last lines output is what you should be using...
If you want to compile something rather than experimenting with tools, don't link it using the linker directly. Use gcc, which will call the linker for you with the right options:
Compile step:
gcc -c hello.c
Link step:
gcc -o hello.exe hello.o
Or all in one:
gcc -o hello.exe hello.c
You can also use a simple Makefile, then run make:
all: hello.exe

Am I writing this makefile correctly?

I am learning how to write C code in Linux and I am learning makefiles at a very beginner level.
I am having problems when making shared libraries.
The exercise is to make a simple function calculator C program with files:
main.c
add.c
subt.c
mult.c
div.c
The names of the files define the function they do.
The function in the file subt.c is in the static library:
libsubstatic.a
The function in the file mult.c is in the shared library:
libmultshared.so
For this program, I write the following makefile:
calc.exe: main.o add.o div.o libsubstatic.a libmultshared.so
gcc -o calc.exe main.o add.o div.o libsubstatic.a -Wl,-rpath,/home/ahmed/Desktop/labTask3 -lmultshared.so
main.o: main.c header.h
gcc -c main.c
add.o: add.c header.h
gcc -c add.c
libsubstatic.a: subt.o
ar cr libsubstatic.a subt.o
subt.o: subt.c header.h
gcc -c subt.c
libmultshared.so: mult.o
gcc -shared -fPIC -o libmultshared.so mult.o
mult.o: mult.c header.h
gcc -c -fPIC mult.c
div.o: div.c header.h
gcc -c div.c
The path where the code and makefile is placed:
/home/ahmed/Desktop/labTask3
I get the following message after I type "make" in the terminal:
gcc -o calc.exe main.o add.o div.o libsubstatic.a -Wl, -rpath, /home/ahmed/Desktop/labTask3 -lmultshared.so
gcc: error: unrecognized command line option ‘-rpath,’
make: *** [calc.exe] Error 1
What am I missing? Did I write this makefile correctly?
Please explain shared libraries, my concept might be faulty.
Please help.
Note that, I'm new to linux and I don't have much experience in makefiles.
EDIT: I removed the spaces as directed in the first answer. Now the terminal says:
gcc -o calc.exe main.o add.o div.o libsubstatic.a -Wl,-rpath,/home/ahmed/Desktop/labTask3 -lmultshared.so
/usr/bin/ld: cannot find -lmultshared.so
collect2: error: ld returned 1 exit status
make: *** [calc.exe] Error 1
Should I do something with the "-lmultshared.so"? What should I do?
-Wl, -rpath, /home/ahmed/Desktop/labTask3
Get rid of the spaces. This should all be one long argument.
-Wl,-rpath,/home/ahmed/Desktop/labTask3
See this excellent answer by #KerrekSB for a detailed explanation about passing arguments to the linker with -Wl.

Error compiling c using gcc on AIX

I'm trying to build a simple c application using gcc on aix
gcc -I. -c hello.c -o hello.o
gcc -o helloWorld hello.o -L helloHelper.so -ldl
I get the following errors
ld 0711-317 ERROR: Undefined symbol: .PrintHello
PrintHello is a method in the library helloHelper.
I can build the application in windows.
The option -L is for indicating directories where to search for libraries. To link a dynamic library directly, just put it in the linker command:
gcc -o helloWorld hello.o helloHelper.so -ldl
Other option would be to use -lhelloHelper but then the library should be called libhelloHelper.so.
Try this:
gcc -o helloworld hello.o -L. -lhelloHelper -ldl

Resources