【发布时间】:2020-09-27 20:53:46
【问题描述】:
我有这样的asm代码:
format ELF
public _start
extrn _kernel_main
section ".text" executable
_start:
movzx edx, dl
push edx
push esi
push ebx
call _kernel_main
@@:
jmp @b
;section ".data" writable
还有这样的ld脚本:
ENTRY(_start)
KERNEL_BASE = 0xFFC00000;
SECTIONS {
.text KERNEL_BASE : {
*(.text)
*(.code)
*(.rdata*)
}
.data ALIGN(0x1000) : {
*(.data)
}
.bss ALIGN(0x1000) : {
*(.bss)
}
.empty ALIGN(0x1000) - 1 : {
BYTE(0)
}
}
这个 c 代码可以正常工作:
void kernel_main() {
char *screen_buffer = (void*)0xB8000;
char msg[] = "Hello, World!";
unsigned int i = 24 * 80;
screen_buffer[i * 2] = *msg;
int j = 0;
while (msg[j] != '\0') {
screen_buffer[i * 2] = *(msg + j);
j++;
i++;
}
}
但如果我尝试使用此代码:
void kernel_main() {
char *screen_buffer = (void*)0xB8000;
char *msg = "Hello world!";
unsigned int i = 24 * 80;
while (*msg) {
screen_buffer[i * 2] = *msg;
msg++;
i++;
}
}
它会停止正确执行并且不显示任何字符。
c 代码做同样的事情。
我看不出 char msg[] = "Hello, World!"; 和 char *msg = "Hello, World!"; 之间没有任何区别 谁能告诉我哪里错了。
【问题讨论】:
-
字符串常量通常进入
.rodata而不是.rdata。确保使用正确的链接器脚本。 -
在
char msg[]的情况下,字符串甚至可能最终使用常量存储构建(即嵌入到立即指令中),即使链接描述文件损坏也可以工作。 -
实际上
char msg[] = "Hello, World!";存在于堆栈中。编译器可以直接初始化它,也可以使用来自.rodata的副本初始化它。在这种情况下,它可能使用了立即移动,这就是它起作用的原因。 -
char *msg是一个单独的对象,它保存着只读字符串文字的地址,它本身在.rodata中有一个地址。全局范围内的char msg[] = ...将在.data中的字符串数据本身上放置一个名称(标签/符号)。内存中没有存储指针(元数据中的符号表除外。)但是作为一个函数中的本地,就像你正在做的那样,它只是从立即数或通过处理来自自动编号标签的数据在堆栈上初始化.rodata。 Look at your compiler output:. -
看看编译器在这两种情况下生成的目标代码,以从另一个角度来回答您的问题:有什么区别。重申其他人所说的,
msg[] = ""版本是一个带有数组初始化器的数组声明,其中数组初始化器看起来像(采用语法形式)字符串文字,但实际上不是字符串文字(它只是标识字节用于初始化数组,编译器想要做的任何方式),msg* = ""版本是一个指针声明,其中一个真正的字符串文字地址作为初始化器。
标签: c assembly linker kernel ld