arm体系架构与实践第9章习题

alanli07 / 2024-11-10 / 原文

实验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);

结果是:

所以如果想用链接脚本中的标识需要使用数组的声明方式

实现打印函数

实现不定长的参数函数调用

  1. 首先需要改变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 = .;

  1. 注意text段和rodata段的对齐,否则后续搬移代码的时候会出问题
  2. 19行将text放置到0x90000处,但是链接的地址是按照text_boot后面的位置来的。
  3. 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的链接地址