Is function declaration essential to C programming? - c

I used to believe that we should declare a function which is defined in another file before use it, but recently I changed my way of thinking due to an experience of programming. For three files, C and ASM:
main.c
extern test_str;
/*extern myprint*/ --> If I add the line, gcc will report an error: called object ‘myprint’ is not a function
void Print_String() {
myprint("a simple test", test_str);
}
kernel.asm
extern Print_String
[section .text]
global _start
global test_str
test_str dd 14
_start:
call Print_String
jmp $
another.asm
[section .text]
global myprint
myprint:
mov edx, [esp + 8]
mov ecx, [esp + 4]
mov ebx, 1
mov eax, 4
int 0x80
ret
compile
nasm -f elf another.asm -o another.o
gcc -c -g main.c -o main.o
nasm -f elf kernel.asm -o kernel.o
ld -o final main.o kernel.o another.o
result
./final
a simple test
In my view, if I want to use the function myprint in main.c, I should declare it using extern beforehand, because myprint is defined in another file, but the result is exactly opposite. Just as main.c shows above. If I add the line extern myprint, I will get an error. However, without that declaration, I will get the right result. What's more, I didn't define function myprint in main.c, why can I use that function? Shouldn't I declare it beforehand?

When you call a function without a prototype the compiler makes some assumptions and guesses about the parameters of that function. So you should declare it, but declare it as a function:
void myprint(const char *, const char *); /* Or whatever. */

Well, you can use the function myprint, though its not defined function in main.c, with no error. This is because the compiler, while creating the object file fills in a NULL value against the symbol myprint in the object file created.
This NULL value is replaced at all places in the binary with the actual address of the function only during the linking phase. The linker refers to the symbol table across all the object files and resolves the symbol (wherever referred) with the actual address.
Certainly you shall see warnings/errors with the -Werror -Wall options to gcc. Although, you can get more insight using objdump as follows:
objdump -D main.o | less
Hope that helps to clear your doubt.

Related

Trouble with C compilation via gcc command line

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
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.

Commands to compile ASM file with C program [duplicate]

This question already has an answer here:
32-bit absolute addresses no longer allowed in x86-64 Linux?
(1 answer)
Closed 4 years ago.
with à 64 Linux system and using NASM.
I'm trying too link my ASM (hello.asm) file with a C file (main.c) and compile to a execution file.
I create a ASM file that print "Hello" with printf by using printHello function.
extern printf, exit
section .data
format db "Hello", 10, 0
section .text
global printHello
printHello:
sub rsp, 8
mov rsi, 0x12345677
mov rdi, format
xor rax, rax
call printf
mov rdi, 0
call exit
I create a simple main.c and call my function "printHello" to print "Hello"
#include <stdio.h>
void printHello();
int main()
{
printHello();
}
My command for compile :
$ nasm -f elf64 hello.asm
$ gcc -c main.c
$ gcc -o executable main.o hello.o
$ ./executable
And it prints :
./executable: Symbol `printf' causes overflow in R_X86_64_PC32 relocation
./executable: Symbol `exit' causes overflow in R_X86_64_PC32 relocation
[1] 6011 segmentation fault ./executable
I'm already learning ASM. Is the problem come from my command or my code ?
I resolved the problem by using your #Jester solution :
gcc -no-pie -o executable main.o hello.o
and thanks Ped7g for explanation.

Calling a C main() function from an x86 32-bit assembly _start [duplicate]

This question already has answers here:
x86 Linux assembler get program parameters from _start
(1 answer)
Get argv[2] address in assembler x64
(1 answer)
Closed 5 years ago.
I am trying to write a homework assignment which is to:
write a simple Assembly program that all it does is call a C program,
and send to it the command line arguments so that it may run properly
with (argc and argv).
How can this be done? We were given this asm as part of the assignment:
section .text
global _start
extern main
_start:
;;code to setup argc and argv for C program's main()
call main
mov eax,1
int 0x80
So what I want to know is, where are argc and argv located? Also, do I just need to put the pointer to argc in the eax register like when returning a value to a regular C function and the C program will work the rest out?
In the end, after compiling my C program with the following Makefile (as I said, I am new to Assembly and this is the Makefile given to us by the teacher, I do not fully understand it):
%.o: %.asm
nasm -g -O1 -f elf -o $# $<
%.o: %.c
gcc -m32 -g -nostdlib -fno-stack-protector -c -o $# $<
all: lwca
lwca: lwc.o start.o
ld -melf_i386 -o $# $^
Running ./lwca arg1 arg2 should result in argc = 3 and argv[1]=arg1 argc[2]=arg2
ANSWER:
No answer quite solved my problem, in the end the what worked was:
pop dword ecx ; ecx = argc
mov ebx,esp ; ebx = argv
push ebx ; char** argv
push ecx ; int argc
call main

ld makes all my functions link to the last one in header file

I've started working on a home-brew OS for learning purposes. So it works like this :
Once the kernel is loaded I create a stack and call my kmain()
In kmain I try calling function foo() defined in header.h
//Header.h
#ifndef INCLUDE_HEADER_H
#define INCLUDE_HEADER_H
int foo(char* buf);
int bar();
#endif
Using nm on my kernel I can clearly see that foo() is in the binary but when I disassemble kmain with gdb I see that foo isn't called, instead bar is.
This problem is recurrent on all headers containing multiple functions.
I compile on windows 10 in a Cygwin environment. I use the following arguments passed to nasm/gcc/ld in my makefile
CC = gcc
CFLAGS = -m32 -nostdlib -nostdinc \
-nostartfiles -fno-leading-underscore -nodefaultlibs\
-Wall -Wextra -Wno-unused-variable -Wno-unused-function\
-c
LD = i686-elf-ld
LDFLAGS = -Tlink.ld -melf_i386
AS = nasm
ASFLAGS = -f elf
Any ideas why ?
EDIT :
//screen.h
#ifndef SCREEN_H
#define SCREEN_H
int test();
void print(char c);
#endif
And
//kmain.c
#include "screen.h"
int kmain(){
int b = test();
print('A');
return 0xcafebabe;
}
nm kernel.elf
$ nm kernel.elf
e4524ffe a CHECKSUM
00000000 a FLAGS
0010011c b kernel_stack
00004000 a KERNEL_STACK_SIZE
00100000 T kmain
001000c8 T loader
001000dd t loader.loop
1badb002 a MAGIC_NUMBER
001000b0 T outb
00100072 T print
0010002c T strlen
00100068 T test
0010005c T testFunc
gdb disassembly of kmain:
(gdb) disassemble kmain
Dump of assembler code for function kmain:
0x00100000 <kmain+0>: push %ebp
0x00100001 <kmain+1>: mov %esp,%ebp
0x00100003 <kmain+3>: sub $0x28,%esp
0x00100006 <kmain+6>: call 0x10006b <print+1> ;should call test but calls print instead
0x0010000b <kmain+11>: mov %eax,-0xc(%ebp)
0x0010000e <kmain+14>: movl $0x41,(%esp) ;pushes 'A'
0x00100015 <kmain+21>: call 0x100084 <print+26> ;calls print('A')
0x0010001a <kmain+26>: mov $0xcafebabe,%eax
0x0010001f <kmain+31>: leave
0x00100020 <kmain+32>: ret
0x00100021 <kmain+33>: nop
0x00100022 <kmain+34>: nop
0x00100023 <kmain+35>: nop
End of assembler dump.
0x00100006 <kmain+6>: call 0x10006b <print+1> ;should call test but calls print instead
<print+1> is just the label. This instruction does call the test function as can be seen from the address 0x10006b :
00100068 T test
00100072 T print
It'll be clearer if you look at the disassembly of the compiled "screen.c".
I found that the problem was in the compiler tool-chain I was using. It's what created the weird linking problem.
Here are the instructions I followed to compile a clean new Binutils + Gcc and it's working now !

Linking with cygwin

I have a small program that's made of an assembly function and a C function which calls it.
Now the program compiles and works perfectly on a UNIX system but when using the makefile in cygwin i get the following error:
gcc -m32 -g -c -o main.o main.c
gcc -g -m32 -o ass0 main.o myasm.o
main.o: In function main':
/cygdrive/c/ass0/main.c:15: undefined reference to_strToLeet'
collect2: error: ld returned 1 exit status
makefile:3: recipe for target 'ass0' failed
make: *** [ass0] Error 1
code of the main.c file :
#include <stdio.h>
# define MAX_LEN 100 // Maximal line size
extern int strToLeet (char*);
int main(void) {
char str_buf[MAX_LEN];
int str_len = 0;
printf("Enter a string: ");
fgets(str_buf, MAX_LEN, stdin); // Read user's command line string
str_len = strToLeet (str_buf); // Your assembly code function
printf("\nResult string:%s\nNumber of letters converted to Leet: %d\n",str_buf,str_len);
}
start of assembly code:
section .data ; data section, read-write
an: DD 0 ; this is a temporary var
section .text ; our code is always in the .text section
global strToLeet ; makes the function appear in global scope
extern printf ; tell linker that printf is defined elsewhere
strToLeet: ; functions are defined as labels
push ebp ; save Base Pointer (bp) original value
mov ebp, esp ; use base pointer to access stack contents
pushad ; push all variables onto stack
mov ecx, dword [ebp+8] ; get function argument
makefile code :
all: ass0
ass0: main.o myasm.o
gcc -g -m32 -o ass0 main.o myasm.o
main.o: main.c
gcc -m32 -g -c -o main.o main.c
myasm.o: myasm.s
nasm -g -f elf -l ass0list -o myasm.o myasm.s
help would be most appriciated
Solved by user 'tvin' -
Try to modify your prototype to become extern int strToLeet (char*) asm ("strToLeet"); – tivn

Resources