1

I'm currently trying to get into the basics regarding C-compilation without the use of an IDE. As I only learned C- and embedded-programming with an IDE I thought it would be a good idea to learn and give me a better understanding of how the whole build process is working behind the scenes. I mainly want to learn how to implement a complete IDEless toolchain for an STM32 controller.

So my idea was to start simple and try to understand the C-only build toolchain and its possible configurations. For this purpose I searched for tutorials and found this and this one.

I tried to follow along the first tutorial on my windows system but encountered some problems quite early that I have trouble understanding.

I created the following hello.c testfile:

#include <stdio.h>
#include <stdint.h>

int main ( void )
{

    printf("Hello World!\n");
    return 0;
}

First I tried the simple full compilation using gcc -o hello.exe hello.c (1.6 from the tutorial) Everything works fine, so I decided to test the compilation steps one after the other (1.7 from the tutorial)

I called all commands in the following order: cpp hello.c > hello.i (preprocessing) -> gcc -S hello.i (Compilation) -> as -o hello.o hello.s (Assembly) -> ld -o hello.exe hello.o (Linking)

Every step until the linking seems to work but the linker gives me the following errors:

ld: hello.o:hello.c:(.text+0xa): undefined reference to `__main' ld:

hello.o:hello.c:(.text+0x47): undefined reference to `puts' ld:

hello.o:hello.c:(.text+0x5c): undefined reference to `printf'

Did I do something wrong here? And is there a reason the ">" operator is used for preprocessing and assembling but not if I just compile using gcc -o hello.exe hello.c

Do one even use these steps seperately that often? I read that instead of cpp hello.c > hello.i I could also use gcc -E main.c > main.i so why use the cpp command, are there any advantages?

Next I set this problem aside and tried to add includes. For this purpose I created the following 2 files: myFunc.c:

uint8_t myFunc( uint8_t param )
{
    uint8_t retVal = 0;
    retVal = param + 1;
    return retVal;
}

myFunc.h

#include <stdint.h>

uint8_t myFunc( uint8_t param );

And changed the hello.c to:

#include <stdio.h>
#include <stdint.h>
#include "myFunc.h"

int main ( void )
{
    uint8_t testVal = 0;
    testVal = myFunc(testVal);

    printf("Hello World!\n");
    printf("Test Value is %d \n", testVal);
    return 0;
}

I first tried the gcc -o hello.exe hello.c but get the error:

undefined reference to `myFunc' collect2.exe: error: ld returned 1 exit status

So I figured I should add the include path (even if it is the same directory). After a short search and the help of the second site I tried gcc -Wall -v -IC:\Users\User\Desktop\C-Only_Toolchain hello.c -o hello.exe But get the same error...

Is there something wrong with the way my include paths are added? (obviously yes)

Lastly I tried to test the GNU make command from the tutorial. I opened the editor and inserted all contents shown in the tutorial. As the editor saves the file as a .txt editor I tried to just delete the file extension.

The makefile looks like this:

all: hello.exe

hello.exe: hello.o
    gcc -o hello.exe hello.o

hello.o: hello.c
    gcc -c hello.c

clean:
    rm hello.o hello.exe

But if I enter make in my console I get the error that the command "make" is written incorrectly or could not be found. I used tab for the indentation just as the tutorial suggests but it will not even recognize that there is a makefile. Is this because it was originally a .txt file before I deleted the extension?

I would be happy if someone could help me with my confusing regarding this rather simple issues... Furthermore I would be very thankful if you have some good suggestions on how to get into this topic more efficiently or have some good sources to share.

Thank you in advance and stay healthy :)

Best Regards Evox402

Evox402
  • 111
  • 1
  • 13
  • 1
    My "Hello :)" got deleted from the post so I just post it here :) – Evox402 Nov 18 '20 at 14:13
  • 5
    Wow! That's a lot of questions at once. And you are more or less asking for a complete course on compilation, linking, make... If you want to increase your chances of receiving useful answers you should probably consider splitting your huge question in simpler, more focused, ones. – Renaud Pacalet Nov 18 '20 at 14:22
  • 2
    Here is a hint for your first question about the linking (this is what `ld` does) that fails: try to compile with the `-v` option: `gcc -v -o hello.exe hello.c`. In this _verbose_ mode `gcc` will tell you a lot about what it does. You'll probably discover that linking is not as simple as `ld -o hello.exe hello.o` because you must link more than one single object to get your executable. – Renaud Pacalet Nov 18 '20 at 14:25
  • 2
    About your other question on includes, you forgot to tell gcc where to find included header files but it was not a problem because gcc found it in the current directory. The problem was, again, a linking one. As you split your project in separate source files you must compile each of them (`gcc -c -o hello.o hello.c` and `gcc -c -o myFunc.o myFunc.c`) and link the resulting object files: `gcc -o hello.exe myFunc.o hello.o`. – Renaud Pacalet Nov 18 '20 at 14:32
  • 1
    Your last problem with make comes from the fact that make is not installed or your command line interface does not know where to find it. As you are apparently under Windows did you try `make.exe`? (just guessing, I do not know anything about Windows, except that executables usually have this extension). And, anyway, your make file is incomplete (it compiles only `hello.c`). – Renaud Pacalet Nov 18 '20 at 14:35
  • Hello Renauld :) Thank you very much for the fast answer. I kind of hoped that the tutorials I found are a basic start course for the topic. Unfortunately I ran into the problems quite early... But thanks for the insight, I could compile my modules without issues with the help of your third comment. I even noticed that you can call ```gcc -o hello.exe hello.c myFunc.c```. I will next try to relocate my other modules and get this running. Regarding the ld-command. Is this used commonly? As I see, I could achieve the same using the mentioned gcc-commands from your third comment – Evox402 Nov 18 '20 at 14:57
  • Regarding make. I tried to change it make.exe but it doesn't work either. I also added myFunc to the makefile. I will search for more make tutorials after I get the gist of the linking part. Regarding the size of my question: I thought it would be smarter to collect my problems rather than split then up and generate overlapping answers in different questions. But I will remember it for the next time :) – Evox402 Nov 18 '20 at 15:03
  • Update to make: I found this post here: https://stackoverflow.com/questions/32127524/how-to-install-and-use-make-in-windows And the solution by Eduardo seems to work fine. I just tried the copy-Solution but will try the PATH solution as well. And maybe starting to get into working with Linux :) – Evox402 Nov 18 '20 at 15:11
  • 1
    People usually let `gcc` do all the work. It is not that frequent that you need to invoke `cpp`, `as` or `ld`. So, if you are a beginner I strongly recommend that 1) you use only `gcc` 2) you understand how to compile individual files (`gcc -c`) and link the object files (`gcc`) 3) you first understand how to do things by hand before automating with `make`. Avoid compiling and linking several source files at once. It works only in simple cases like your example and it hides what you would like to understand in the first place. – Renaud Pacalet Nov 18 '20 at 15:16
  • Thank you very much for your help. I could get quite familiar with the building by using the gcc commands and dividing the steps between compilation and linking. I could even get the makefile running :) – Evox402 Nov 24 '20 at 14:58

1 Answers1

2

So, these are a lot of questions. (In the following I use linux, so some outputs are just similar, not identical, like paths and the assembly output, but because of your usage of gcc, it's quite transferable to windows).

I called all commands in the following order: cpp hello.c > hello.i (preprocessing) -> gcc -S hello.i (Compilation) -> as -o hello.o hello.s (Assembly) -> ld -o hello.exe hello.o (Linking)

As a repetition: What are you doing here?

cpp hello.c > hello.i

You run the preprocessor over the C file. It just does a text-replace of macros/ #defines and includes files.

This looks like this. (A bit shortened as it has around 800 lines)

...Snip....
struct _IO_FILE;
typedef struct _IO_FILE FILE;
struct _IO_FILE
{
  int _flags;


  char *_IO_read_ptr;
  char *_IO_read_end;
  char *_IO_read_base;
  char *_IO_write_base;
  char *_IO_write_ptr;
  char *_IO_write_end;
  char *_IO_buf_base;
  char *_IO_buf_end;


  char *_IO_save_base;
  char *_IO_backup_base;
  char *_IO_save_end;

  struct _IO_marker *_markers;

  struct _IO_FILE *_chain;

  int _fileno;
  int _flags2;
  __off_t _old_offset;


  unsigned short _cur_column;
  signed char _vtable_offset;
  char _shortbuf[1];

  _IO_lock_t *_lock;







  __off64_t _offset;

  struct _IO_codecvt *_codecvt;
  struct _IO_wide_data *_wide_data;
  struct _IO_FILE *_freeres_list;
  void *_freeres_buf;
  size_t __pad5;
  int _mode;

  char _unused2[15 * sizeof (int) - 4 * sizeof (void *) - sizeof (size_t)];
};
extern FILE *stdin;
extern FILE *stdout;
extern FILE *stderr;
...Snip...
extern int printf (const char *__restrict __format, ...);
...Snip...
int main ( void )
{

    printf("Hello World!\n");
    return 0;
}

Now all important definitions are included, so the C compiler can run. gcc -S hello.i. It just converts your C code to assembly. (It will look a bit different on windows)

    .file   "hello.c"
    .text
    .section    .rodata
.LC0:
    .string "Hello World!"
    .text
    .globl  main
    .type   main, @function
main:
.LFB0:
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    leaq    .LC0(%rip), %rdi
    call    puts@PLT
    movl    $0, %eax
    popq    %rbp
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE0:
    .size   main, .-main
    .ident  "GCC: (Debian 10.2.0-17) 10.2.0"
    .section    .note.GNU-stack,"",@progbits

Now you have to convert the assembly code to machine code:

as -o hello.o hello.s

This command just generates an so called object file with your code and important metadata, the linker will need.

ld -o hello.exe hello.o

Now you invoke the linker with your object file as argument and hello.exe as output file. It will look for the entry point (_start on linux-like, WinMain for example on windows, or sometimes _main). But also the functions from the C-standard-library are missing. But why? You don't say the linker, that you want to include it. If you invoke the linker ld as explicit as you did, you have to pass all libraries you want to include. You have to add for example -lc to include the stdlib, and so on.

Did I do something wrong here?

You just forgot to add the C library to the libraries the linker should link with your object-file.

And is there a reason the ">" operator is used for preprocessing

> is not from cpp. It is from the shell. Try running without > hello.i. The preprocessor will just output it on the console. The > redirects to the specified file (Here hello.i).

I could also use gcc -E main.c > main.i so why use the cpp command, are there any advantages?

There is no difference. gcc calls the preprocessor internally.

Do one even use these steps seperately that often?

These steps are sometimes used in makefiles, but not as separated as you did, but often only in compiling+linking as two separate steps to reduce compile-time.

first tried the gcc -o hello.exe hello.c but get the error:

It compiles, the C compiler knows, there is at least a definition for myFunc and because of this, it emits valid assembly code. But the linker, as soon as it resolves the references to functions, it doesn't find it and emits the error. You have to add the myFunc.c to your commandline:

gcc -o hello.exe hello.c myFunc.c

But if I enter make in my console I get the error that the command "make" is written incorrectly or could not be found. I used tab for the indentation just as the tutorial suggests but it will not even recognize that there is a makefile. Is this because it was originally a .txt file before I deleted the extension?

You have to add the directory of make.exe to the path. Suppose it has the path:

C:\Foo\bar\baz\make.exe

Then you add it to the path (Execute it in the commandline):

set PATH=%PATH%;C:\Foo\bar\baz

This will only work until you close the commandline, or you can set it permanently as outlined here for example.

JCWasmx86
  • 3,473
  • 2
  • 11
  • 29
  • 1
    Thank you very much :) I could get a makefile running while my source and include files are located inside seperate folders and completely using the gcc-commands. I think I got a good understanding now to start with more complex materials – Evox402 Nov 24 '20 at 14:56
  • Glad I was able to help you. – JCWasmx86 Nov 24 '20 at 18:11