arm体系架构与实践第9章习题
实验9-1
解释每一条语句的含义
SECTIONS
{
// 链接地址放到0x80000 与运行地址一致
. = 0x80000,
// 将每个文件对应的段放到一起
.text.boot : {*(.text.boot)}
.text : {*(.text)}
.rodata : {*(.rodata)}
.data : { *(.data)}
// 8字节对齐
. = ALIGN(0x8);
// 记录bss的起始地址和终止地址,应该是可以方便后续的清空操作
bss_begin = .;
.bss : {*(.bss*)}
bss_end = .;
// 4kb对齐 按页分配
. = ALIGN(4096)
init_gp_dir = .;
. += 4096;
}
实验9-2
使用链接脚本中的地址
如果想使用链接脚本中的标识需要使用
1.
extern char 标识[];
标识 = 链接地址
2.
extern char* 标识;
&标识 = 链接地址
标识 = 链接地址处的内容
- 数组:标识一定是第一个参数的地址,也就是链接脚本中的地址
- 指针:标识会是地址的内容(指令),而标识的地址才是链接脚本中地址
示例:
extern char *text_boot_end;
...
printk(" 指针 %x 内容 %x\n", &text_boot_end, text_boot_end);
结果是:
gdb的结果
但是如果是数组
extern char text_boot_begin[];
extern char text_boot_end[];
...
printk(" %x - %x\n", text_boot_begin, text_boot_end);
结果是:
所以如果想用链接脚本中的标识需要使用数组的声明方式
实现打印函数
实现不定长的参数函数调用
- 首先需要改变Makefile中的参数
-COPS += -g -Wall -nostdlib -nostdinc -Iinclude
+# COPS += -g -Wall -nostdlib -nostdinc -Iinclude
+COPS += -g -Wall -nostdlib -Iinclude
-nostdinc:开启后不会主动去寻找系统头文件的路径
由于需要使用到 stdarg.h 所以需要将这个参数删除
2. 实现printk
#include "uart.h"
#include <stdarg.h>
void printk(const char *format, ...)
{
va_list args;
va_start(args, format);
for (const char *p = format; *p; p++) {
if (*p == '%') {
p++;
switch (*p) {
case 'd': {
int i = va_arg(args, int);
uart_send_int(i);
break;
}
case 's': {
char *s = va_arg(args, char *);
uart_send_string(s);
break;
}
case 'x': {
unsigned int i = va_arg(args, int);
uart_send_hex(i);
break;
}
default:
uart_send('%');
uart_send(*p);
break;
}
} else {
uart_send(*p);
}
}
va_end(args);
}
实现对应的uart的函数
输出十进制
void uart_send_int(int num)
{
if (num < 0) {
uart_send('-');
num = -num;
}
if (num == 0) {
uart_send('0');
return;
}
char buffer[10]; // 假设整数不超过10位数
int i = 0;
// 逐位取出整数的每一位数字
while (num > 0) {
buffer[i++] = (num % 10) + '0'; // 转换成字符
num /= 10;
}
// 反向输出
i = 8;
while (i > 0) {
uart_send(buffer[--i]);
}
}
实现输出十六进制
// 将整数转换为十六进制并输出
void uart_send_hex(unsigned int num)
{
if (num == 0) {
uart_send('0');
return;
}
char buffer[16]; // 假设32位整型数不超过8位十六进制
for (int i = 0; i < 16; ++i) {
buffer[i] = '0';
}
int i = 0;
// 十六进制字符表,小写或大写由参数决定
const char *digits = "0123456789abcdef";
while (num > 0) {
buffer[i++] = digits[num % 16];
num /= 16;
}
// 逆序输出
i = 16;
while (i > 0) {
uart_send(buffer[--i]);
}
}
输出实现
void ShowImageLayout()
{
// 其他和这个一样
printk(".text.boot: 0x%x - 0x%x ( %d B)\n", text_boot_begin,
text_boot_end, text_boot_end - text_boot_begin);
}
9-3
链接脚本:
diff --git a/src/linker.ld b/src/linker.ld
index 10b7c10..874bc01 100644
--- a/src/linker.ld
+++ b/src/linker.ld
@@ -1,10 +1,26 @@
+TEXT_ROM = 0x90000;
+
SECTIONS
{
/*. = 0xffff000000080000,*/
. = 0x80000,
+ _text_boot = .;
.text.boot : { *(.text.boot) }
- .text : { *(.text) }
- .rodata : { *(.rodata) }
+ _etext_boot = .;
+
+ _text = .;
+ .text : AT(TEXT_ROM) {
+ *(.text)
+ }
+ _etext = .;
+
+ . = ALIGN(0x8);
+ _rodata = .;
+ .rodata : AT(0x80000 + SIZEOF(.text) + SIZEOF(.text.boot)) {
+ *(.rodata)
+ }
+ _erodata = .;
+
.data : { *(.data) }
. = ALIGN(0x8);
bss_begin = .;
- 注意text段和rodata段的对齐,否则后续搬移代码的时候会出问题
- 19行将text放置到0x90000处,但是链接的地址是按照text_boot后面的位置来的。
- 26行:rodata会默认放置在text的加载地址的后面(0x90xxx),但是kernel中有字符串常量,字符串常量在rodata段中,但是后续搬移如果不搬移rodata段的话,这个字符串就无法正常打印,所以可以直接再链接脚本中将rodata段的加载地址设置在原本的链接地址上。
boot.S
proc_hang:
b proc_hang
+.align 3
master:
+ adr x0, TEXT_ROM
+ adr x1, _text
+ adr x2, _etext
+1:
+ ldr x4, [x0], #8
+ str x4, [x1], #8
+ cmp x1, x2
+ b.cc 1b
+
678设置需要拷贝的地址
9-13进行逐8字节拷贝,从0x90000拷贝到text的链接地址