Failed to pass constant string as parameter in C function - c

I copied the bootasm.S from https://github.com/jeffallen/xv6/blob/master/bootasm.S,
#include "asm.h"
# Start the first CPU: switch to 32-bit protected mode, jump into C.
# The BIOS loads this code from the first sector of the hard disk into
# memory at physical address 0x7c00 and starts executing in real mode
# with %cs=0 %ip=7c00.
#define SEG_KCODE 1 // kernel code
#define SEG_KDATA 2 // kernel data+stack
#define CR0_PE 1 // protected mode enable bit
.code16 # Assemble for 16-bit mode
.globl start
start:
cli # BIOS enabled interrupts; disable
# Set up the important data segment registers (DS, ES, SS).
xorw %ax,%ax # Segment number zero
movw %ax,%ds # -> Data Segment
movw %ax,%es # -> Extra Segment
movw %ax,%ss # -> Stack Segment
# Physical address line A20 is tied to zero so that the first PCs
# with 2 MB would run software that assumed 1 MB. Undo that.
seta20.1:
inb $0x64,%al # Wait for not busy
testb $0x2,%al
jnz seta20.1
movb $0xd1,%al # 0xd1 -> port 0x64
outb %al,$0x64
seta20.2:
inb $0x64,%al # Wait for not busy
testb $0x2,%al
jnz seta20.2
movb $0xdf,%al # 0xdf -> port 0x60
outb %al,$0x60
# Switch from real to protected mode. Use a bootstrap GDT that makes
# virtual addresses map dierctly to physical addresses so that the
# effective memory map doesn't change during the transition.
lgdt gdtdesc
movl %cr0, %eax
orl $CR0_PE, %eax
movl %eax, %cr0
# Complete transition to 32-bit protected mode by using long jmp
# to reload %cs and %eip. The segment registers are set up with no
# translation, so that the mapping is still the identity mapping.
ljmp $(SEG_KCODE<<3), $start32
.code32 # Tell assembler to generate 32-bit code now.
start32:
# Set up the protected-mode data segment registers
movw $(SEG_KDATA<<3), %ax # Our data segment selector
movw %ax, %ds # -> DS: Data Segment
movw %ax, %es # -> ES: Extra Segment
movw %ax, %ss # -> SS: Stack Segment
xor %eax, %eax # Zero segments not ready for use
movw %ax, %fs # -> FS
movw %ax, %gs # -> GS
## sti TaoWang: It should NOT call STI here, since NO IDT is ready.
# Set up the stack pointer and call into C.
movl $start, %esp
call bootmain
spin:
jmp spin
# Bootstrap GDT
.p2align 2 # force 4 byte alignment
gdt:
SEG_NULLASM # null seg
SEG_ASM(STA_X|STA_R, 0x0, 0xffffffff) # code seg
SEG_ASM(STA_W, 0x0, 0xffffffff) # data seg
gdtdesc:
.word (gdtdesc - gdt - 1) # sizeof(gdt) - 1
.long gdt # address gdt
.fill 510-(.-start)
.word 0xaa55
and change the bootmain.c as follows,
#include "types.h"
char serial_buffer[256];
static void my_memcpy(void *dst, void *src, u32 length)
{
u32 i = 0;
for (i = 0; i < length; i ++) {
*(char *)dst = *(char *)src;
}
if (serial_buffer[0] == 'A') {
asm ("cli\nhlt\n");
} else {
asm ("vmcall");
}
}
int bootmain(void)
{
my_memcpy(serial_buffer, "Abcedife", 8);
return 0;
}
void handle_page_fault(void)
{
return;
}
After the code is built through the Makefile (I listed below), the code to load the output binary is here,
unsigned char tempbuf[0x400];
void file_load(char *vmfname)
{
int vmfd = -1;
size_t cnt = 0, offset = 0;
vmfd = open( vmfname, O_RDWR );
if (vmfd < 0) {
exit(2);
}
do {
cnt = read(vmfd, tempbuf, sizeof(tempbuf));
// initialize the virtual-machine registers
memcpy((void *)(CODE_START + offset), tempbuf, cnt);
offset += cnt;
} while (cnt > 0);
close(vmfd);
printf("Loading %ld bytes of VM to run\n", offset);
}
To my surprise, the while loop does NOT execute at all.
Here is my linker.ld, and I run them in Linux 4.4.0.
ENTRY(start);
SECTIONS
{
. = 0x7C00;
.text : AT(0x7C00)
{
_text = .;
*(.text);
_text_end = .;
}
.data :
{
_data = .;
*(.bss);
*(.bss*);
*(.data);
*(.rodata*);
*(COMMON)
_data_end = .;
}
PROVIDE(data = .);
/* The data segment */
.data : {
*(.data)
}
PROVIDE(edata = .);
.bss : {
*(.bss)
}
PROVIDE(end = .);
/DISCARD/ : {
*(.eh_frame .note.GNU-stack)
}
}
The Makefile,
all: test
OBJDUMP=objdump
OBJCOPY=objcopy
CFLAGS = -fno-pic -static -fno-builtin -fno-strict-aliasing -Wall -MD -ggdb -m32 -Werror -fno-omit-frame-pointer
CFLAGS += $(shell $(CC) -fno-stack-protector -E -x c /dev/null >/dev/null 2>&1 && echo -fno-stack-protector)
ASFLAGS = -m32 -gdwarf-2 -Wa,-divide
LDFLAGS += -m $(shell $(LD) -V | grep elf_i386 2>/dev/null)
guest: test_app.c
$(CC) -g2 -Wall -Wextra -Werror $^ -o $#
$(CC) $(CFLAGS) -fno-pic -nostdinc -I. -c bootasm.S
$(CC) $(CFLAGS) -fno-pic -I. -c bootmain.c
$(LD) $(LDFLAGS) -N -e start -Tlinker.ld -o bootblock.o bootasm.o bootmain.o
$(OBJDUMP) -S bootblock.o > bootblock.asm
$(OBJCOPY) -S -O binary -j .text bootblock.o bootblock.bin
clean:
rm -f *.o
rm -f *.d
rm -f test
rm -f *.bin
rm -f bootblock.asm
I don't know why the constant string failed to be passed as the parameter or its content is all '0' ?
If I use an array of char, put the array name as the parameter to myfputs(chararray), it will work well.

I answered this question by referring to Michael's answer about adding -j .data to Makefile, so that data section will be added to the final binary, which can solve the problem.
With the change in the Makefile, now the code can work as expected.
Here is the command line for building the final binary.
guest: test_app.c
$(CC) -g2 -Wall -Wextra -Werror $^ -o $#
$(CC) $(CFLAGS) -nostdinc -I. -c bootasm.S
$(CC) $(CFLAGS) -I. -c bootmain.c
$(LD) $(LDFLAGS) -N -e start -Tlinker.ld -o bootblock.o bootasm.o bootmain.o
$(OBJDUMP) -S bootblock.o > bootblock.asm
$(OBJCOPY) -S -O binary -j .text -j .data -j .bss bootblock.o bootblock.bin

Related

Correct way to debug assembly code (nasm) with GDB

I am trying to learn the x86 assembly language, and would like to set up a little environment to do it efficiently. For that I have written some simple code (factorial, fibonnaci,etc...), added a main.c for testing and a Makefile to compile.
But I was never able to debug my code with gdb, no matter what flags I have tried...
Here is the code of facto.asm
global fact
section .text
fact:
push rbx
mov rcx, rdi
xor rax, rax
xor rbx, rbx
inc rax
inc rbx
loop:
mov rdx, rbx
mul rdx
inc rbx
dec rcx
jnz loop
pop rbx
ret
The Makefile:
EXE := facto
BIN := $(EXE).o
ASM := $(EXE).asm
SRC := main
all: $(EXE)
$(BIN): $(ASM)
nasm -f elf64 -gdwarf $(ASM) -o $(BIN)
$(EXE): $(SRC).c $(BIN)
gcc -no-pie -g $(SRC).c $(BIN) -o $(EXE)
.PHONY: clean
clean:
$(RM) *.o
$(RM) $(EXE)
And finally the main.c in which I am doing the tests:
#include <stdio.h>
extern int fact();
int main(void)
{
size_t res = fact(5);
printf("%zu\n",res);
}
When running GDB and trying to "skip" into the assembler code, it just does not get in there and goes directly to the printf, even though I have set the -g flag for gcc and -gdwarf for nasm. Furthermore I am manually setting a break on that line with b.
What is it that I am missing? I would be glad for any clues!
If this can help by any means,I am currently working on an arch linux machine.
Thanks in advance!

Cant run compiled file in Ubuntu

I have a problem I cant fix with a simple exercise my teacher assigned us.
I have this main.c that takes in a simple assembly function and I compile it with a make file, when I hit make run I get the following error :
"make: execvp: ./main: invalid argument make:***
[makefile:12:run] Error 127"
This is my make file:
main: main.o asm.o
gcc main.o asm.o -o main
main.o: main.c asm.h
gcc -Wall -g -c main.c -o main.o
asm.o: asm.s
gcc -Wall -g -c asm.s -o asm.o
run: main
./main
clean:
rm *.o main
My main.c file:
#include "asm.h"
int op1 = 0, op2 = 0, res = 0;
int main()
{
printf("Valor op1:");
scanf("%d", &op1);
printf("Valor op2:");
scanf("%d", &op2);
sum();
printf("sum = %d:0x%x\n", res, res);
return 0;
}
My asm.s:
.section .data
.global op1
.global op2
.global res
.section .text
.global sum # void sum(void)
sum:
movl op1(%rip), %ecx #place op1 in ecx
movl op2(%rip), %eax #place op2 in eax
addl %ecx, %eax #add ecx to eax. Result is in eax
movl %eax, res(%rip) # copy the result to res
ret
my asm.h:
#ifndef ASM_H
#define ASM_H
void sum();
#endif

GDB showing wrong address to local variables

The below code is an snippet from the 2nd stage loader program. It is running in Real Mode and gets called by the bootloader program. The problem is that GDB is showing wrong addresses to variables allocated in stack.
|------|------|------|
|Name |GDB |Actual|
|------|------|------|
|char a|0x7be7|0x7bfb|
|char b|0x7be6|0x7bfa|
|------|------|------|
Source:
__attribute__((noreturn))
void __main()
{
char a = 'A';
char b = 'B';
while(1);
}
Disassembly:
00008000 <__main>:
8000: 66 55 push ebp
8002: 66 89 e5 mov ebp,esp
8005: 66 83 ec 10 sub esp,0x10
8009: 67 c6 45 ff 41 mov BYTE PTR [ebp-0x1],0x41 <--- char a
800e: 67 c6 45 fe 42 mov BYTE PTR [ebp-0x2],0x42 <--- char b
8013: eb fe jmp 8013 <__main+0x13>
This file gets loaded at physical location 0x8000 by a custom bootloader.
QEMU and GDB
> qemu-system-i386 -fda build/boot.flp -s -S
> gdb loader.sym
target remote localhost:1234
set architecture i386
b __main
c
s
s
p &a
0x7be7 "" <-- Not EBP - 1
p &b
0x7be6 "" <-- Not EBP - 2
info reg
eax 0x0 0
ecx 0x0 0
edx 0x7de3 32227
ebx 0x8000 32768
esp 0x7bec 0x7bec
ebp 0x7bfc 0x7bfc <-- char a is at 0x7bfb and char b is at 0x7bfa
esi 0x0 0
edi 0x0 0
eip 0x8013 0x8013 <__main+19>
eflags 0x202 [ IF ]
cs 0x0 0
ss 0x0 0
ds 0x0 0
es 0x0 0
fs 0x0 0
gs 0x0 0
I do not really understand what is going on. Could it be the -m16 option?
Compilation
gcc -std=c99 \
-nostartfiles \
-c \
-g \
-ffreestanding \
-fno-pie \
-fno-stack-protector \
-m16 \
-march=i386 \
-Wpedantic \
-Wextra \
-Wall \
-O0 bootloader/x86/phase2/loader.c -o $TEMPDIR/loader.o || exit
ld -m elf_i386 --nmagic --script=build/loader.ld $TEMPDIR/loader.o -o $TEMPDIR/loader.lo || exit
objcopy --only-keep-debug $TEMPDIR/loader.lo $SYMDIR/loader.sym||exit
objcopy -O binary $TEMPDIR/loader.lo $OBJDIR/LOADER.flt||exit
Linker Script (build/loader.ld)
ENTRY (__main)
SECTIONS
{
. = 0x8000; /* Loader is loaded at 0x0000:0x8000 */
.text :AT(0x0)
{
*.o (.text);
}
.data :
{
*.o (.data);
*.o (.bss);
*.o (.rodata);
}
/DISCARD/ :
{
*(.eh_frame)
}
}
Things I tried:
Using -m32 instead of -m16 and .code16gcc at the top of the C file.
Verified that GCC, GDB works perfectly when compilling a native application.
Used --oformat binary option in ld instead of objcopy
PS:
GCC Version: 8.3.0
GNU ld Version: 2.31.1
Linux Debian 10

logical and physical adress in C code in real mode

Suppose I write boot loader on C. What happens when I create some global variable? What is it's logical address? How does it correspond to physical address? For example if I created some string (global)
const char* s = "some string";
Am I right that s stored in .data section? What would be the physical address of s and what would be a logical one? Should we do some extra work to make this addresses correspond each other.
My OS is Linux and I compile my code like this:
as --32 boot.S -o boot.o
gcc -c -m32 -g -Os -ffreestanding -Wall -Werror -I. -o mbr.o mbr.c
ld -Tlinker.ld -nostdlib -o mbr boot.o mbr.o
boot.S is just where I initilize some registers and call c code:
.code16
.text
.global _start
_start:
cli
xor %ax, %ax
mov %ax, %ds
mov %ax, %es
mov %ax, %ss
mov $0x7c00, %sp
ljmp $0, $mmain
mmain -- function in C code. My linker script is:
OUTPUT_FORMAT(binary)
OUTPUT_ARCH(i8086)
ENTRY(_start)
SECTIONS
{
. = 0x7C00;
.text : { *(.text) }
.sig : AT(0x7DFE)
{
SHORT(0xaa55);
}
}

Assembly function call from c

I cannot combine my kernel_entry.asm and main.c. My main.c calls an asm function Sum. Both nasm and gcc compiles respective files. However, the linker gives an error.
Kernel_entry.asm:
[bits 32]
[extern _start]
[global _Sum]
....
_Sum:
push ebp
mov ebp, esp
mov eax, [ebp+8]
mov ecx, [ebp+12]
add eax, ecx
pop ebp
ret
main.c:
....
extern int Sum();
void start() {
....
int x = Sum(4, 5);
....
}
To compile source files, I use following commands:
nasm kernel_entry.asm -f win32 -o kernel_entry.o
gcc -ffreestanding -c main.c -o main.o
....
ld -T NUL -o kernel.tmp -Ttext 0x1000 kernel_entry.o main.o mem.o port_in_out.o screen.o idt.o
Linker gives following error:main.o:main.c:(.text+0xa82): undifened reference to 'Sum'. I tried everything but couldn't find any solution. When I remove asm function call from main.c, it works.
The TL;DR version of the answer is that mixing nasm's -f win32 generates an object file that is not compatible with the GNU toolchain on Windows - you need to use -f elf if you want to link using ld. That is described in NASM's documentation here under sections 7.5 and 7.9.
The hint for me was that by running nm kernel_entry.o generated:
00000000 a .absolut
00000000 t .text
00000001 a #feat.00
U _start
U _Sum
Which basically shows Sum as an undefined symbol. After compiling as ELF, I got:
U _start
00000000 T _Sum
indicating Sum as a recognised symbol in the text section.

Resources