Include contents of char array in flat binary file - c

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

Related

Is there a GCC version of the NASM ORG instruction?

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>

Can objdump use bss variable names in text section?

I am using objdump to generate the disassembly of C code and wondering if there is a way to get the names of variables from the heap (.bss section) to be used in the .text section disassembly, rather than the hex addresses.
For example,
int main(void)
{
while (1)
{
request_ID = Receive(0, buffer, MSG_SIZE, 0);
}
return 0;
}
This is compiled, and then using objdump -D file.o I get the disassembly, including the .bss section:
400: 55 push ebp
401: 89 e5 mov ebp,esp
403: 83 ec 18 sub esp,0x18
...
43d: a1 cc 24 00 00 mov eax,ds:0x24cc
...
Disassembly of section .bss:
000024cc <request_pID>:
What I would like is for the hex addresses of variables to be replaced by their name:
43d: a1 cc 24 00 00 mov eax, <request_pID>
I could write a sed script or something similar to achieve this, but was wondering if there was a simpler option.
Even better would be for both the address and the variable name to be printed to aid in debugging.
43d: a1 cc 24 00 00 mov eax, ds:0x24cc <request_pID>
The code is for an operating system development being tested in Bochs, so if there is some other way of loading symbols into Bochs' debugger that would be a good workaround, although I would still like the objdump output to be created as well.
thanks, Paul

execute binary machine code from C

following this instructions I have managed to produce only 528 bytes in size a.out (when gcc main.c gave me 8539 bytes big file initially).
main.c was:
int main(int argc, char** argv) {
return 42;
}
but I have built a.out from this assembly file instead:
main.s:
; tiny.asm
BITS 64
GLOBAL _start
SECTION .text
_start:
mov eax, 1
mov ebx, 42
int 0x80
with:
me#comp# nasm -f elf64 tiny.s
me#comp# gcc -Wall -s -nostartfiles -nostdlib tiny.o
me#comp# ./a.out ; echo $?
42
me#comp# wc -c a.out
528 a.out
because I need machine code I do:
objdump -d a.out
a.out: file format elf64-x86-64
Disassembly of section .text:
00000000004000e0 <.text>:
4000e0: b8 01 00 00 00 mov $0x1,%eax
4000e5: bb 2a 00 00 00 mov $0x2a,%ebx
4000ea: cd 80 int $0x80
># objdump -hrt a.out
a.out: file format elf64-x86-64
Sections:
Idx Name Size VMA LMA File off Algn
0 .note.gnu.build-id 00000024 00000000004000b0 00000000004000b0 000000b0 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
1 .text 0000000c 00000000004000e0 00000000004000e0 000000e0 2**4
CONTENTS, ALLOC, LOAD, READONLY, CODE
SYMBOL TABLE:
no symbols
file is in little endian convention:
me#comp# readelf -a a.out
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: EXEC (Executable file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x4000e0
Start of program headers: 64 (bytes into file)
Start of section headers: 272 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 56 (bytes)
Number of program headers: 2
Size of section headers: 64 (bytes)
Number of section headers: 4
Section header string table index: 3
now I want to execute this like this:
#include <unistd.h>
// which version is (more) correct?
// this might be related to endiannes (???)
char code[] = "\x01\xb8\x00\x00\xbb\x00\x00\x2a\x00\x00\x80\xcd\x00";
char code_v1[] = "\xb8\x01\x00\x00\x00\xbb\x2a\x00\x00\x00\xcd\x80\x00";
int main(int argc, char **argv)
{
/*creating a function pointer*/
int (*func)();
func = (int (*)()) code;
(int)(*func)();
return 0;
}
however I get segmentation fault. My question is: is this section of text
4000e0: b8 01 00 00 00 mov $0x1,%eax
4000e5: bb 2a 00 00 00 mov $0x2a,%ebx
4000ea: cd 80 int $0x80
(this machine code) all I really need? What I do wrong (endiannes??), maybe I just need to call this in different way since SIGSEGV?
The code must be in a page with execute permission. By default, stack and read-write static data (like non-const globals) are in pages mapped without exec permission, for security reasons.
The simplest way is to compile with gcc -z execstack, which links your program such that stack and global variables (static storage) get mapped in executable pages, and so do allocations with malloc.
Another way to do it without making everything executable is to copy this binary machine code into an executable buffer.
#include <unistd.h>
#include <sys/mman.h>
#include <string.h>
char code[] = {0x55,0x48,0x89,0xe5,0x89,0x7d,0xfc,0x48,
0x89,0x75,0xf0,0xb8,0x2a,0x00,0x00,0x00,0xc9,0xc3,0x00};
/*
00000000004004b4 <main> 55 push %rbp
00000000004004b5 <main+0x1> 48 89 e5 mov %rsp,%rbp
00000000004004b8 <main+0x4> 89 7d fc mov %edi,-0x4(%rbp)
00000000004004bb <main+0x7> 48 89 75 f0 mov %rsi,-0x10(%rbp)
'return 42;'
00000000004004bf <main+0xb> b8 2a 00 00 00 mov $0x2a,%eax
'}'
00000000004004c4 <main+0x10> c9 leaveq
00000000004004c5 <main+0x11> c3 retq
*/
int main(int argc, char **argv) {
void *buf;
/* copy code to executable buffer */
buf = mmap (0,sizeof(code),PROT_READ|PROT_WRITE|PROT_EXEC,
MAP_PRIVATE|MAP_ANON,-1,0);
memcpy (buf, code, sizeof(code));
__builtin___clear_cache(buf, buf+sizeof(code)-1); // on x86 this just stops memcpy from optimizing away as a dead store
/* run code */
int i = ((int (*) (void))buf)();
printf("get this done. returned: %d", i);
return 0;
}
output:
get this done. returned: 42
RUN SUCCESSFUL (total time: 57ms)
Without __builtin___clear_cache, this could break with optimization enabled because gcc would think the memcpy was a dead store and optimize it away. When compiling for x86, __builtin___clear_cache does not actually clear any cache; there are zero extra instructions; it just marks the memory as "used" so stores to it aren't considered "dead". (See the gcc manual.)
Another option would be to mprotect the page containing the char code[] array, giving it PROT_READ|PROT_WRITE|PROT_EXEC. This works whether it's a local array (on the stack) or global in the .data.
Or if it's const char code[] in the .rodata section, you might just give it PROT_READ|PROT_EXEC.
(In versions of binutils ld from before about 2019, the .rodata got linked as part of the same segment as .text, and was already mapped executable. But recent ld gives it a separate segment so it can be mapped without exec permission so const char code[] doesn't give you an executable array anymore, but it used to so you may this old advice in other places.)
The point is that DEP protection is enabled!
you can goto Configurations -> Linker -> Advance -> DEP turn off ,
it's ok now .
void main(){
int i = 11;
//The following is the method to generate the machine code directly!
//mov eax, 1; ret;
const char *code = "\xB8\x10\x00\x00\x00\xc3";
__asm call code; //test successful~..vs 2017
__asm mov i ,eax;
printf("i=%d", i);
}

How do I force the entry point to my code to be at address 0 when compiling with rtems-gcc

I have a simple code that I am trying to compile with lm32-rtems4.11-gcc.
I have the code, the compile command and the lst below. When I compile I see a bunch of code added on the top instead of the startup code that I want in there. The code I want the processor to start with after reset is at location 3f4 instead of 0. What I wanted help on is to figure out how the rest of the code got in and find a way to remove it or move all that code to addresses after my code. I appreciate the help.
Thanks
The code:
//FILE: crt.S
.globl _start
.text
_start:
xor r0, r0, r0
mvhi sp, hi(_fstack)
ori sp, sp, lo(_fstack)
mv fp,r0
mvhi r1, hi(_fbss)
ori r1, r1, lo(_fbss)
mvhi r2, hi(_ebss)
ori r2, r2, lo(_ebss)
1:
bge r1, r2, 2f
sw (r1+0), r0
addi r1, r1, 4
bi 1b
2:
calli main
mvhi r1, 0xdead
ori r2, r0, 0xbeef
sw (r1+0), r2
//FILE: hello_world.c
void putc(char c)
{
char *tx = (char*)0xff000000;
*tx = c;
}
void puts(char *s)
{
while (*s) putc(*s++);
}
void main(void)
{
puts("Hello World\n");
}
//FILE: linker.ld
OUTPUT_FORMAT("elf32-lm32")
ENTRY(_start)
__DYNAMIC = 0;
MEMORY {
pmem : ORIGIN = 0x00000000, LENGTH = 0x8000
dmem : ORIGIN = 0x00008000, LENGTH = 0x8000
}
SECTIONS
{
.text :
{
_ftext = .;
*(.text .stub .text.* .gnu.linkonce.t.*)
_etext = .;
} > pmem
.rodata :
{
. = ALIGN(4);
_frodata = .;
*(.rodata .rodata.* .gnu.linkonce.r.*)
*(.rodata1)
_erodata = .;
} > dmem
.data :
{
. = ALIGN(4);
_fdata = .;
*(.data .data.* .gnu.linkonce.d.*)
*(.data1)
_gp = ALIGN(16);
*(.sdata .sdata.* .gnu.linkonce.s.*)
_edata = .;
} > dmem
.bss :
{
. = ALIGN(4);
_fbss = .;
*(.dynsbss)
*(.sbss .sbss.* .gnu.linkonce.sb.*)
*(.scommon)
*(.dynbss)
*(.bss .bss.* .gnu.linkonce.b.*)
*(COMMON)
. = ALIGN(4);
_ebss = .;
_end = .;
} > dmem
}
The compile command
lm32-rtems4.11-gcc -Tlinker.ld -fno-builtin -o hello_world.elf crt.S hello_world.c
lm32-rtems4.11-objdump -DS hello_world.lst hello_world.elf
The lst file
00000000 <rtems_provides_crt0>:
#include <signal.h> /* sigset_t */
#include <time.h> /* struct timespec */
#include <unistd.h> /* isatty */
void rtems_provides_crt0( void ) {} /* dummy symbol so file always has one */
0: c3 a0 00 00 ret
00000004 <rtems_stub_malloc>:
#define RTEMS_STUB(ret, func, body) \
ret rtems_stub_##func body; \
ret func body
/* RTEMS provides some of its own routines including a Malloc family */
RTEMS_STUB(void *,malloc(size_t s), { return 0; })
4: 34 01 00 00 mvi r1,0
8: c3 a0 00 00 ret
0000000c <malloc>:
c: 34 01 00 00 mvi r1,0
10: c3 a0 00 00 ret
.
.
.
//omitting other such unrelated code that was inserted into the code and going to the
//code at 3f4 that is the code I wanted at 0
000003f0 <__assert_func>:
3f0: c3 a0 00 00 ret
000003f4 <_start>:
3f4: 98 00 00 00 xor r0,r0,r0
3f8: 78 1c 00 00 mvhi sp,0x0
3fc: 3b 9c ff fc ori sp,sp,0xfffc
400: b8 00 d8 00 mv fp,r0
404: 78 01 00 00 mvhi r1,0x0
408: 38 21 84 48 ori r1,r1,0x8448
40c: 78 02 00 00 mvhi r2,0x0
410: 38 42 84 48 ori r2,r2,0x8448
414: 4c 22 00 04 bge r1,r2,424 <_start+0x30>
418: 58 20 00 00 sw (r1+0),r0
41c: 34 21 00 04 addi r1,r1,4
420: e3 ff ff fd bi 414 <_start+0x20>
424: f8 00 00 28 calli 4c4 <main>
428: 78 01 de ad mvhi r1,0xdead
42c: 38 02 be ef mvu r2,0xbeef
430: 58 22 00 00 sw (r1+0),r2
.
.
.
As far as the .elf object you have generated is concerned, execution starts from 0x3f4, not from location 0. That's a result of your linker map specifying the entry point as the _start symbol. Whatever parses the .elf object should jump to that location when transferring execution to the program.
Now, perhaps an .elf object is not what you want to end up with - if the result isn't to be loaded by something which knows how to parse an .elf object, then you may need some other format, such as a flat binary image.
It's quite common when using a gcc elf toolchain with a small embedded chip to turn the .elf object into a flat binary using a command along the lines of
toolchain-prefix-objcopy -O binary something.elf something.bin
It's also possible you may need to create some sort of stub to jump to the _start label, and adjust your linker map to make sure that is the first thing in the image.
More generally though, you can probably find a working example for this toolchain and either this processor or a comparable one. Setting up embedded build systems from scratch is a bit tricky, so don't do it the hard way if there's any chance of finding an example to follow.
So I could not figure out why the compiler does not move the .start label to 0 when the linker.ld clearly tells it to do so. But I did figure a work around.
I created a section name for the startup code as shown in BOLD below. I then created a section in memory starting at 0 which I reserved only for this start up code. That seemed to do the trick. I ran the code and got a hello world :) . All the changes I made are in BOLD and also commented //Change 1 //Change 2 and //Change 3.
//FILE: crt.S
.section .init// Change 1
.globl _start
.text
_start:
xor r0, r0, r0
mvhi sp, hi(_fstack)
ori sp, sp, lo(_fstack)
mv fp,r0
mvhi r1, hi(_fbss)
ori r1, r1, lo(_fbss)
.
.
//linker.ld
OUTPUT_FORMAT("elf32-lm32")
ENTRY(_start)
__DYNAMIC = 0;
MEMORY {
init : ORIGIN = 0x00000000, LENGTH = 0x40 //Change 2
pmem : ORIGIN = 0x00000040, LENGTH = 0x8000
dmem : ORIGIN = 0x00008000, LENGTH = 0x8000
}
SECTIONS
{
.init : {*(.init)}>init //Change 3
.text :
{
_ftext = .;
*(.text .stub .text.* .gnu.linkonce.t.*)
_etext = .;
} > pmem

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