Is there a GCC version of the NASM ORG instruction? - c

I'm currently making an OS, and when I tried to add C support, I ran into a bit of a problem... In assembly, each program on my OS starts with ORG 32768 (the NASM compiler preprocessor instruction for offsetting the origin of the code), but I can't seem to find anything on a way to do this using the GCC compiler for C. So, my question is, how would one achieve this (offsetting the code's origin) in C using GCC? (and yes, I have looked it up before asking, even checked GNU's official GCC's C preprocessor documentation)

ORG and .ORG go back to the days when you wrote programs in assembly and didnt necessarily need a linker.
The gnu tools dont support it AFAIK.
start.s
.globl _start
_start:
mov $0xA000,%rsp
callq fun
jmp .
fun.c
unsigned int fun ( void )
{
return(7);
}
fun.ld
MEMORY
{
ram : ORIGIN = 0x8000, LENGTH = 0x2000
}
SECTIONS
{
.text : { *(.text*) } > ram
.rodata : { *(.rodata*) } > ram
.data : { *(.data*) } > ram
.bss : { *(.bss*) } > ram
}
build commands
as start.s -o start.o
gcc -O2 -nostdlib -nostartfiles -ffreestanding -c fun.c -o fun.o
ld -T fun.ld start.o fun.o -o fun
which produces this program:
0000000000008000 <_start>:
8000: 48 c7 c4 00 a0 00 00 mov $0xa000,%rsp
8007: e8 04 00 00 00 callq 8010 <fun>
800c: eb fe jmp 800c <_start+0xc>
800e: 66 90 xchg %ax,%ax
0000000000008010 <fun>:
8010: b8 07 00 00 00 mov $0x7,%eax
8015: c3 retq
I used an entry point of 0x8000 (32768).
If by gcc you meant the gnu tools and just wanted to do assembly language then that makes it a bit simpler, you only need the binutils package not gcc. But you still need the linker and use the ORIGIN in the very simpler linker script example above where you would have used .ORG inline with the assembly.
start.s
.globl _start
_start:
mov $0xA000,%rsp
mov $0x7,%eax
add $0x1,%eax
jmp .
same linker script as above
as start.s -o start.o
ld -T fun.ld start.o -o fun
producing
0000000000008000 <_start>:
8000: 48 c7 c4 00 a0 00 00 mov $0xa000,%rsp
8007: b8 07 00 00 00 mov $0x7,%eax
800c: 83 c0 01 add $0x1,%eax
800f: eb fe jmp 800f <_start+0xf>

Related

How to define a subroutine/macro in sperate .asm file?

I'm programming in AVR for Atmega32 using AtmelStudio. I was wondering how you can write a subroutine or function in a separate file and then call it within main.asm?
current issue is I have a subroutine genArrays inside of genArray.asm .
Using .include "genArray.asm" at the start of main.asm causes the program to run genArray immediately at the start of main.asm when i don’t want it to be called until i actually call it using the call instruction (example below)
main.asm:
.include "genArray.asm"
.org 0x0000
start:
...
... ---- ; do some stuff
...
...
call genArrays ---- ; call to genArrays subroutine that is defined in genArray.asm (separate file)
... ---- ; return here and continue with program
... ---- ; do some more stuff
genArray.asm:
genArrays: ---- ; start of subroutine
...
...
...
... -----; do some stuff
...
ret
With gnu assembler/binutils. I am very rusty on my avr, but...
so.s
.globl _start
_start:
nop
nop
rcall fun
nop
nop
here:
rjmp here
fun.s
.globl fun
fun:
nop
nop
ret
build
avr-as so.s -o so.o
avr-as fun.s -o fun.o
avr-ld -Ttext=0 so.o fun.o -o so.elf
avr-objdump -d so.elf
so.elf: file format elf32-avr
Disassembly of section .text:
00000000 <__ctors_end>:
0: 00 00 nop
2: 00 00 nop
4: 03 d0 rcall .+6 ; 0xc <fun>
6: 00 00 nop
...
0000000a <here>:
a: ff cf rjmp .-2 ; 0xa <here>
0000000c <fun>:
c: 00 00 nop
e: 00 00 nop
10: 08 95 ret
assembly language is specific to the assembler, not the target, so you need to use the language for the assembler you are using, the above is gnu assembler for avr. Likewise how you link them is very much tool specific. gnu ld has a myriad of features, AVR is a PITA to build for (from scratch) so you may want to use an already built toolchain and linker script. (on linux I simply apt-got a toolchain).
As utterly horrible as this is:
avr-gcc -nostdlib so.s fun.s -o so.elf
avr-objdump -d so.elf
so.elf: file format elf32-avr
Disassembly of section .text:
00000000 <__ctors_end>:
0: 00 00 nop
2: 00 00 nop
4: 03 d0 rcall .+6 ; 0xc <fun>
6: 00 00 nop
...
0000000a <here>:
a: ff cf rjmp .-2 ; 0xa <here>
0000000c <fun>:
c: 00 00 nop
e: 00 00 nop
10: 08 95 ret
works (thus far).
Try the simple thing: put .include "genArray.asm" at the end of main.asm so its code will come after main in program memory.
That should be good enough for now. You could also take a look at the assembly generated by avr-gcc and see how it defines its functions.

Remove stack frame setup/initilization

I have the following program:
void main1() {
((void(*)(void)) (0xabcdefabcdef)) ();
}
I create it with the following commands:
clang -fno-stack-protector -c -static -nostdlib -fpic -fpie -O0 -fno-asynchronous-unwind-tables main.c -o shellcode.o
ld shellcode.o -o shellcode -S -static -dylib -e main1 -order_file order.txt
gobjcopy -O binary --only-section=.text shellcode shellcode.output
The assembly looks like the following:
//
// ram
// ram: 00000000-00000011
//
**************************************************************
* FUNCTION *
**************************************************************
undefined FUN_00000000()
undefined AL:1 <RETURN>
FUN_00000000
00000000 55 PUSH RBP
00000001 48 89 e5 MOV RBP,RSP
00000004 48 b8 ef MOV RAX,0xabcdefabcdef
cd ab ef
cd ab 00 00
0000000e ff d0 CALL RAX
00000010 5d POP RBP
00000011 c3 RET
How do I get clang to remove the PUSH RBP, MOV RBP,RSP and POP RBP instructions as they are unnecessary?
I can do this if I write the program in assembly with the following lines:
.globl start
start:
movq $0xabcdefabcdef, %rax
call *%rax
ret
and with the following build commands:
clang -static -nostdlib main.S -o crashme.o
gobjcopy -O binary --only-section=.text crashme.o crashme.output
and the resulting assembly:
//
// ram
// ram: 00000000-0000000c
//
assume DF = 0x0 (Default)
00000000 48 b8 ef MOV RAX,0xabcdefabcdef
cd ab ef
cd ab 00 00
0000000a ff d0 CALL RAX
0000000c c3 RET
but I would much rather write C code instead of assembly.
You forgot to enable optimization. Any optimization level like -O3 enables -fomit-frame-pointer.
It will also optimize the tailcall into a jmp instead of call/ret though. If you need to avoid that for some reason, maybe you can use -fomit-frame-pointer at the default -O0.
For shellcode you might want -Os to optimize for code size. Or even clang's -Oz; that will have a side-effect of avoiding some 0 bytes in the machine code by using push imm8 / pop reg to put small constants in registers, instead of mov reg, imm32.

gcc subtracting from esp before call

I am planning to use C to write a small kernel and I really don't want it to bloat with unnecessary instructions.
I have two C files which are called main.c and hello.c. I compile and link them using the following GCC command:
gcc -Wall -T lscript.ld -m16 -nostdlib main.c hello.c -o main.o
I am dumping .text section using following OBJDUMP command:
objdump -w -j .text -D -mi386 -Maddr16,data16,intel main.o
and get the following dump:
00001000 <main>:
1000: 67 66 8d 4c 24 04 lea ecx,[esp+0x4]
1006: 66 83 e4 f0 and esp,0xfffffff0
100a: 67 66 ff 71 fc push DWORD PTR [ecx-0x4]
100f: 66 55 push ebp
1011: 66 89 e5 mov ebp,esp
1014: 66 51 push ecx
1016: 66 83 ec 04 sub esp,0x4
101a: 66 e8 10 00 00 00 call 1030 <hello>
1020: 90 nop
1021: 66 83 c4 04 add esp,0x4
1025: 66 59 pop ecx
1027: 66 5d pop ebp
1029: 67 66 8d 61 fc lea esp,[ecx-0x4]
102e: 66 c3 ret
00001030 <hello>:
1030: 66 55 push ebp
1032: 66 89 e5 mov ebp,esp
1035: 90 nop
1036: 66 5d pop ebp
1038: 66 c3 ret
My questions are: Why are machine codes at the following lines being generated?
I can see that subtraction and addition completes each other, but why are they generated? I don't have any variable to be allocated on stack. I'd appreciate a source to read about usage of ECX.
1016: 66 83 ec 04 sub esp,0x4
1021: 66 83 c4 04 add esp,0x4
main.c
extern void hello();
void main(){
hello();
}
hello.c
void hello(){}
lscript.ld
SECTIONS{
.text 0x1000 : {*(.text)}
}
As I mentioned in my comments:
The first few lines (plus the push ecx) are to ensure the stack is aligned on a 16-byte boundary which is required by the Linux System V i386 ABI. The pop ecx and lea before the ret in main is to undo that alignment work.
#RossRidge has provided a link to another Stackoverflow answer that details this quite well.
In this case you seem to be doing real mode development. GCC isn't well suited for this but it can work and I will assume you know what you are doing. I mention some of the pitfalls of using -m16 in this Stackoverflow answer. I put this warning in that answer regarding real mode development with GCC:
There are so many pitfalls in doing this that I recommend against it.
If you remain undeterred and wish to continue forward you can do a few things to minimize the code. The 16-byte alignment of the stack at the point a function call is made is part of the more recent Linux System V i386 ABIs. Since you are generating code for a non-Linux environment you can change the stack alignment to 4 using compiler option -mpreferred-stack-boundary=2 . The GCC manual says:
-mpreferred-stack-boundary=num
Attempt to keep the stack boundary aligned to a 2 raised to num byte boundary. If -mpreferred-stack-boundary is not specified, the default is 4 (16 bytes or 128 bits).
If we add that to your GCC command we get gcc -Wall -T lscript.ld -m16 -nostdlib main.c hello.c -o main.o -mpreferred-stack-boundary=2:
00001000 <main>:
1000: 66 55 push ebp
1002: 66 89 e5 mov ebp,esp
1005: 66 e8 04 00 00 00 call 100f <hello>
100b: 66 5d pop ebp
100d: 66 c3 ret
0000100f <hello>:
100f: 66 55 push ebp
1011: 66 89 e5 mov ebp,esp
1014: 66 5d pop ebp
1016: 66 c3 ret
Now all the extra alignment code to get it on a 16-byte boundary has disappeared. We are left with typical function frame pointer prologue and epilogue code. This is often in the form of push ebp and mov ebp,esp pop ebp. we can remove these with the -fomit-frame-pointer define in the GCC manual as:
The option -fomit-frame-pointer removes the frame pointer for all functions which might make debugging harder.
If we add that option we get gcc -Wall -T lscript.ld -m16 -nostdlib main.c hello.c -o main.o -mpreferred-stack-boundary=2 -fomit-frame-pointer:
00001000 <main>:
1000: 66 e8 02 00 00 00 call 1008 <hello>
1006: 66 c3 ret
00001008 <hello>:
1008: 66 c3 ret
You can then optimize for size with -Os. The GCC manual says this:
-Os
Optimize for size. -Os enables all -O2 optimizations that do not typically increase code size. It also performs further optimizations designed to reduce code size.
This has a side effect that main will be placed into a section called .text.startup. If we display both with objdump -w -j .text -j .text.startup -D -mi386 -Maddr16,data16,intel main.o we get:
Disassembly of section .text:
00001000 <hello>:
1000: 66 c3 ret
Disassembly of section .text.startup:
00001002 <main>:
1002: e9 fb ff jmp 1000 <hello>
If you have functions in separate objects you can alter the calling convention so the first 3 Integer class parameters are passed in registers rather than the stack. The Linux kernel uses this method as well. Information on this can be found in the GCC documentation:
regparm (number)
On the Intel 386, the regparm attribute causes the compiler to pass arguments number one to number if they are of integral type in registers EAX, EDX, and ECX instead of on the stack. Functions that take a variable number of arguments will continue to be passed all of their arguments on the stack.
I wrote a Stackoverflow answer with code that uses __attribute__((regparm(3))) that may be a useful source of further information.
Other Suggestions
I recommend you consider compiling each object individually rather than altogether. This is also advantageous since it can be more easily be done in a Makefile later on.
If we look at your command line with the extra options mentioned above you'd have:
gcc -Wall -T lscript.ld -m16 -nostdlib main.c hello.c -o main.o \
-mpreferred-stack-boundary=2 -fomit-frame-pointer -Os
I recommend you do it this way:
gcc -c -Os -Wall -m16 -ffreestanding -nostdlib -mpreferred-stack-boundary=2 \
-fomit-frame-pointer main.c -o main.o
gcc -c -Os -Wall -m16 -ffreestanding -nostdlib -mpreferred-stack-boundary=2 \
-fomit-frame-pointer hello.c -o hello.o
The -c option (I added it to the beginning) forces the compiler to just generate the object file from the source and not to perform linking. You will also notice the -T lscript.ld has been removed. We have created .o files above. We can now use GCC to link all of them together:
gcc -ffreestanding -nostdlib -Wl,--build-id=none -m16 \
-Tlscript.ld main.o hello.o -o main.elf
The -ffreestanding will force the linker to not use the C runtime, the -Wl,--build-id=none will tell the compiler not to generate some noise in the executable for build notes. In order for this to really work you'll need a slightly more complex linker script that places the .text.startup before .text. This script also adds the .data section, the .rodata and .bss sections. The DISCARD option removes exception handling data and other unneeded information.
ENTRY(main)
SECTIONS{
.text 0x1000 : SUBALIGN(4) {
*(.text.startup);
*(.text);
}
.data : SUBALIGN(4) {
*(.data);
*(.rodata);
}
.bss : SUBALIGN(4) {
__bss_start = .;
*(COMMON);
*(.bss);
}
. = ALIGN(4);
__bss_end = .;
/DISCARD/ : {
*(.eh_frame);
*(.comment);
*(.note.gnu.build-id);
}
}
If we look at a complete OBJDUMP with objdump -w -D -mi386 -Maddr16,data16,intel main.elf we would see:
Disassembly of section .text:
00001000 <main>:
1000: e9 01 00 jmp 1004 <hello>
1003: 90 nop
00001004 <hello>:
1004: 66 c3 ret
If you want to convert main.elf to a binary file that you can place in a disk image and read it (ie. via BIOS interrupt 0x13), you can create it this way:
objcopy -O binary main.elf main.bin
If you dump main.bin with NDISASM using ndisasm -b16 -o 0x1000 main.bin you'd see:
00001000 E90100 jmp word 0x1004
00001003 90 nop
00001004 66C3 o32 ret
Cross Compiler
I can't stress this enough but you should consider using a GCC cross compiler. The OSDev Wiki has information on building one. It also has this to say about why:
Why do I need a Cross Compiler?
You need to use a cross-compiler unless you are developing on your own operating system. The compiler must know the correct target platform (CPU, operating system), otherwise you will run into trouble. If you use the compiler that comes with your system, then the compiler won't know it is compiling something else entirely. Some tutorials suggest using your system compiler and passing a lot of problematic options to the compiler. This will certainly give you a lot of problems in the future and the solution is build a cross-compiler.

Include contents of char array in flat binary file

I am using flat binary files as external programs for my OS. When I compile them, like so:
gcc -Wall ctest.c -o ctest.bin -nostdlib -Wl,-Ttext=0x8000,-nostdlib -masm=intel
objcopy -O binary -j .text ctest.bin ctest
But doing this, the contents of the character array aren't put in the file. This is my code:
static volatile char string[4] = "Hi!\0";
static volatile char string2[15] = "Hello, World!\n\0";
int _start()
{
asm("mov eax, [string]");
asm("mov ebx, 0x00");
asm("int 0x01");
asm("mov eax, [string2]");
asm("mov ebx, 0x00");
asm("int 0x01");
return 0;
}
and when I run objdump (I ran it on the elf, but I verified it had the same code as this):
00008000 <_start>:
8000: 55 push ebp
8001: 89 e5 mov ebp,esp
8003: a1 70 90 00 00 mov eax,ds:0x9070
8008: bb 00 00 00 00 mov ebx,0x0
800d: cd 01 int 0x1
800f: a1 74 90 00 00 mov eax,ds:0x9074
8014: bb 00 00 00 00 mov ebx,0x0
8019: cd 01 int 0x1
801b: b8 00 00 00 00 mov eax,0x0
8020: 5d pop ebp
8021: c3 ret
As you can see, the text is nowhere to be found. I was hoping it would do something like this: string db "Hi!", 0 which I would do with nasm.
What should I do so it includes the characters in the output bin file without coding this in assembly?
Thanks in advance.
A binary executable file is typically divided into sections. Your strings have simply been placed into a different section than the code. This makes sense, since the code should be read-only but the strings have been declared non-const and volatile.
I figured out how to do it.
First I created a linker script like this (You can change phys to whatever address you want to load it at):
OUTPUT_FORMAT("binary")
ENTRY(start)
phys = 0x8000;
SECTIONS
{
.text phys : AT(phys) {
code = .;
*(.text)
*(.rodata)
. = ALIGN(0);
}
.data : AT(phys + (data - code))
{
data = .;
*(.data)
. = ALIGN(0);
}
.bss : AT(phys + (bss - code))
{
bss = .;
*(.bss)
. = ALIGN(0);
}
end = .;
}
Then compiled and linked the executable like this:
gcc -m32 -nostdlib -nostdinc -fno-builtin -o exec.o exec.c
ld -T link.ld -melf_i386 -o exec.bin exec.o

Using GCC to produce readable assembly?

I was wondering how to use GCC on my C source file to dump a mnemonic version of the machine code so I could see what my code was being compiled into. You can do this with Java but I haven't been able to find a way with GCC.
I am trying to re-write a C method in assembly and seeing how GCC does it would be a big help.
If you compile with debug symbols (add -g to your GCC command line, even if you're also using -O31),
you can use objdump -S to produce a more readable disassembly interleaved with C source.
>objdump --help
[...]
-S, --source Intermix source code with disassembly
-l, --line-numbers Include line numbers and filenames in output
objdump -drwC -Mintel is nice:
-r shows symbol names on relocations (so you'd see puts in the call instruction below)
-R shows dynamic-linking relocations / symbol names (useful on shared libraries)
-C demangles C++ symbol names
-w is "wide" mode: it doesn't line-wrap the machine-code bytes
-Mintel: use GAS/binutils MASM-like .intel_syntax noprefix syntax instead of AT&T
-S: interleave source lines with disassembly.
You could put something like alias disas="objdump -drwCS -Mintel" in your ~/.bashrc. If not on x86, or if you like AT&T syntax, omit -Mintel.
Example:
> gcc -g -c test.c
> objdump -d -M intel -S test.o
test.o: file format elf32-i386
Disassembly of section .text:
00000000 <main>:
#include <stdio.h>
int main(void)
{
0: 55 push ebp
1: 89 e5 mov ebp,esp
3: 83 e4 f0 and esp,0xfffffff0
6: 83 ec 10 sub esp,0x10
puts("test");
9: c7 04 24 00 00 00 00 mov DWORD PTR [esp],0x0
10: e8 fc ff ff ff call 11 <main+0x11>
return 0;
15: b8 00 00 00 00 mov eax,0x0
}
1a: c9 leave
1b: c3 ret
Note that this isn't using -r so the call rel32=-4 isn't annotated with the puts symbol name. And looks like a broken call that jumps into the middle of the call instruction in main. Remember that the rel32 displacement in the call encoding is just a placeholder until the linker fills in a real offset (to a PLT stub in this case, unless you statically link libc).
Footnote 1: Interleaving source can be messy and not very helpful in optimized builds; for that, consider https://godbolt.org/ or other ways of visualizing which instructions go with which source lines. In optimized code there's not always a single source line that accounts for an instruction but the debug info will pick one source line for each asm instruction.
If you give GCC the flag -fverbose-asm, it will
Put extra commentary information in the generated assembly code to make it more readable.
[...] The added comments include:
information on the compiler version and command-line options,
the source code lines associated with the assembly instructions, in the form FILENAME:LINENUMBER:CONTENT OF LINE,
hints on which high-level expressions correspond to the various assembly instruction operands.
Use the -S (note: capital S) switch to GCC, and it will emit the assembly code to a file with a .s extension. For example, the following command:
gcc -O2 -S foo.c
will leave the generated assembly code on the file foo.s.
Ripped straight from http://www.delorie.com/djgpp/v2faq/faq8_20.html (but removing erroneous -c)
Using the -S switch to GCC on x86 based systems produces a dump of AT&T syntax, by default, which can be specified with the -masm=att switch, like so:
gcc -S -masm=att code.c
Whereas if you'd like to produce a dump in Intel syntax, you could use the -masm=intel switch, like so:
gcc -S -masm=intel code.c
(Both produce dumps of code.c into their various syntax, into the file code.s respectively)
In order to produce similar effects with objdump, you'd want to use the --disassembler-options= intel/att switch, an example (with code dumps to illustrate the differences in syntax):
$ objdump -d --disassembler-options=att code.c
080483c4 <main>:
80483c4: 8d 4c 24 04 lea 0x4(%esp),%ecx
80483c8: 83 e4 f0 and $0xfffffff0,%esp
80483cb: ff 71 fc pushl -0x4(%ecx)
80483ce: 55 push %ebp
80483cf: 89 e5 mov %esp,%ebp
80483d1: 51 push %ecx
80483d2: 83 ec 04 sub $0x4,%esp
80483d5: c7 04 24 b0 84 04 08 movl $0x80484b0,(%esp)
80483dc: e8 13 ff ff ff call 80482f4 <puts#plt>
80483e1: b8 00 00 00 00 mov $0x0,%eax
80483e6: 83 c4 04 add $0x4,%esp
80483e9: 59 pop %ecx
80483ea: 5d pop %ebp
80483eb: 8d 61 fc lea -0x4(%ecx),%esp
80483ee: c3 ret
80483ef: 90 nop
and
$ objdump -d --disassembler-options=intel code.c
080483c4 <main>:
80483c4: 8d 4c 24 04 lea ecx,[esp+0x4]
80483c8: 83 e4 f0 and esp,0xfffffff0
80483cb: ff 71 fc push DWORD PTR [ecx-0x4]
80483ce: 55 push ebp
80483cf: 89 e5 mov ebp,esp
80483d1: 51 push ecx
80483d2: 83 ec 04 sub esp,0x4
80483d5: c7 04 24 b0 84 04 08 mov DWORD PTR [esp],0x80484b0
80483dc: e8 13 ff ff ff call 80482f4 <puts#plt>
80483e1: b8 00 00 00 00 mov eax,0x0
80483e6: 83 c4 04 add esp,0x4
80483e9: 59 pop ecx
80483ea: 5d pop ebp
80483eb: 8d 61 fc lea esp,[ecx-0x4]
80483ee: c3 ret
80483ef: 90 nop
godbolt is a very useful tool, they list only has C++ compilers but you can use -x c flag in order to get it treat the code as C. It will then generate an assembly listing for your code side by side and you can use the Colourise option to generate colored bars to visually indicate which source code maps to the generated assembly. For example the following code:
#include <stdio.h>
void func()
{
printf( "hello world\n" ) ;
}
using the following command line:
-x c -std=c99 -O3
and Colourise would generate the following:
Did you try gcc -S -fverbose-asm -O source.c then look into the generated source.s assembler file ?
The generated assembler code goes into source.s (you could override that with -o assembler-filename ); the -fverbose-asm option asks the compiler to emit some assembler comments "explaining" the generated assembler code. The -O option asks the compiler to optimize a bit (it could optimize more with -O2 or -O3).
If you want to understand what gcc is doing try passing -fdump-tree-all but be cautious: you'll get hundreds of dump files.
BTW, GCC is extensible thru plugins or with MELT (a high level domain specific language to extend GCC; which I abandoned in 2017)
You can use gdb for this like objdump.
This excerpt is taken from http://sources.redhat.com/gdb/current/onlinedocs/gdb_9.html#SEC64
Here is an example showing mixed source+assembly for Intel x86:
(gdb) disas /m main
Dump of assembler code for function main:
5 {
0x08048330 : push %ebp
0x08048331 : mov %esp,%ebp
0x08048333 : sub $0x8,%esp
0x08048336 : and $0xfffffff0,%esp
0x08048339 : sub $0x10,%esp
6 printf ("Hello.\n");
0x0804833c : movl $0x8048440,(%esp)
0x08048343 : call 0x8048284
7 return 0;
8 }
0x08048348 : mov $0x0,%eax
0x0804834d : leave
0x0804834e : ret
End of assembler dump.
Use the -S (note: capital S) switch to GCC, and it will emit the assembly code to a file with a .s extension. For example, the following command:
gcc -O2 -S -c foo.c
I haven't given a shot to gcc, but in case of g++, the command below works for me.
-g for debug build
-Wa,-adhln are passed to assembler for listing with source code
g++ -g -Wa,-adhln src.cpp
For risc-v dissasembly, these flags are nice:
riscv64-unknown-elf-objdump -d -S -l --visualize-jumps --disassembler-color=color --inlines
-d: disassemble, most basic flag
-S: intermix source. Note: must use -g flag while compiling
-l: line numbers
--visualize-jumps: fancy arrows, not too useful but why not. Sometimes get's too messy and actually makes reading the source harder. Taken from Peter Cordes's comment: --visualize-jumps=coloris also an option, to use different colors for different arrows
--disassembler-color=color: give the disassembly some color
--inlines: print out inlines
Maybe usefull:
-M numeric: Use numeric reg names instead of abi names, useful if you are doing cpu dev and don't know the abi names by heart
-M no-aliases: don't use psudoinstructions like li and call
Example:
main.o:
#include <stdio.h>
#include <stdint.h>
static inline void example_inline(const char* str) {
for (int i = 0; str[i] != 0; i++)
putchar(str[i]);
}
int main() {
printf("Hello world");
example_inline("Hello! I am inlined");
return 0;
}
I recommend to use -O0 if you want intermix sources. Intermix sources becomes very messy if using -O2.
Command:
riscv64-unknown-elf-gcc main.c -c -O0 -g
riscv64-unknown-elf-objdump -d -S -l --disassembler-color=color --inlines main.o
Dissasembly:
main.o: file format elf64-littleriscv
Disassembly of section .text:
0000000000000000 <example_inline>:
example_inline():
/Users/cyao/test/main.c:4
#include <stdio.h>
#include <stdint.h>
static inline void example_inline(const char* str) {
0: 7179 addi sp,sp,-48
2: f406 sd ra,40(sp)
4: f022 sd s0,32(sp)
6: 1800 addi s0,sp,48
8: fca43c23 sd a0,-40(s0)
000000000000000c <.LBB2>:
/Users/cyao/test/main.c:5
for (int i = 0; str[i] != 0; i++)
c: fe042623 sw zero,-20(s0)
10: a01d j 36 <.L2>
0000000000000012 <.L3>:
/Users/cyao/test/main.c:6 (discriminator 3)
putchar(str[i]);
12: fec42783 lw a5,-20(s0)
16: fd843703 ld a4,-40(s0)
1a: 97ba add a5,a5,a4
1c: 0007c783 lbu a5,0(a5)
20: 2781 sext.w a5,a5
22: 853e mv a0,a5
24: 00000097 auipc ra,0x0
28: 000080e7 jalr ra # 24 <.L3+0x12>
/Users/cyao/test/main.c:5 (discriminator 3)
for (int i = 0; str[i] != 0; i++)
2c: fec42783 lw a5,-20(s0)
30: 2785 addiw a5,a5,1
32: fef42623 sw a5,-20(s0)
0000000000000036 <.L2>:
/Users/cyao/test/main.c:5 (discriminator 1)
36: fec42783 lw a5,-20(s0)
3a: fd843703 ld a4,-40(s0)
3e: 97ba add a5,a5,a4
40: 0007c783 lbu a5,0(a5)
44: f7f9 bnez a5,12 <.L3>
0000000000000046 <.LBE2>:
/Users/cyao/test/main.c:7
}
46: 0001 nop
48: 0001 nop
4a: 70a2 ld ra,40(sp)
4c: 7402 ld s0,32(sp)
4e: 6145 addi sp,sp,48
50: 8082 ret
0000000000000052 <main>:
main():
/Users/cyao/test/main.c:9
int main() {
52: 1141 addi sp,sp,-16
54: e406 sd ra,8(sp)
56: e022 sd s0,0(sp)
58: 0800 addi s0,sp,16
/Users/cyao/test/main.c:10
printf("Hello world");
5a: 000007b7 lui a5,0x0
5e: 00078513 mv a0,a5
62: 00000097 auipc ra,0x0
66: 000080e7 jalr ra # 62 <main+0x10>
/Users/cyao/test/main.c:11
example_inline("Hello! I am inlined");
6a: 000007b7 lui a5,0x0
6e: 00078513 mv a0,a5
72: 00000097 auipc ra,0x0
76: 000080e7 jalr ra # 72 <main+0x20>
/Users/cyao/test/main.c:13
return 0;
7a: 4781 li a5,0
/Users/cyao/test/main.c:14
}
7c: 853e mv a0,a5
7e: 60a2 ld ra,8(sp)
80: 6402 ld s0,0(sp)
82: 0141 addi sp,sp,16
84: 8082 ret
PS. There are colors in the dissembled code
use -Wa,-adhln as option on gcc or g++ to produce a listing output to stdout.
-Wa,... is for command line options for the assembler part (execute in gcc/g++ after C/++ compilation). It invokes as internally (as.exe in Windows).
See
>as --help
as command line to see more help for the assembler tool inside gcc

Resources