JYY's ICS2020 PA1

开天辟地的篇章

从状态机视角理解程序运行

1
2
3
4
5
6
7
// PC: instruction    | // label: statement
0: mov r1, 0 | pc0: r1 = 0;
1: mov r2, 0 | pc1: r2 = 0;
2: addi r2, r2, 1 | pc2: r2 = r2 + 1;
3: add r1, r1, r2 | pc3: r1 = r1 + r2;
4: blt r2, 100, 2 | pc4: if (r2 < 100) goto pc2; // branch if less than
5: jmp 5 | pc5: goto pc5;

Q:画出这个程序的状态机

需要更新的状态只包括PC,r1和r0,所以用三元组表示程序的所有状态:

(0, x, x) -> (1, 0, x) -> (2, 0, 0) -> (3, 0, 1) -> (4, 1, 1) -> (2, 1, 1) -> (3, 1, 2) -> (4, 3, 2) -> … -> (2, 4851, 98) -> (3, 4851, 99) -> (4, 4950, 99) -> (2, 4950, 99) -> (3, 4950, 100) -> (4, 5050, 100) -> (5, 5050, 100)

  • Thoughts : 如果不是实验中给出了初始的(0, x, x) -> (1, 0, x) -> (2, 0, 0) -> .. , 以我的感觉我极有可能会写(0, 1, x) -> (1, 0, 0) -> .. 我的逻辑是在pc0的时候,r1赋值为0了, 但是在状态机中表示的是一个瞬间的状态所以在pc0的时候, r1还没有被赋值,所以上面的(0, x, x)是对的

RTFSC

函数: getopt()

变量: _IMAGE_START_: 客户程序的固定内存位置

x86寄存器结构实现:

原题:

1
2
3
4
5
6
* TODO: Re-organize the `CPU_state' structure to match the register                                 │
* encoding scheme in i386 instruction format. For example, if we │
* access cpu.gpr[3]._16, we will get the `bx' register; if we access │
* cpu.gpr[1]._8[1], we will get the 'ch' register. Hint: Use `union'. │
* For more details about the register encoding scheme, see i386 manual. │
*/

题目很明显需要我们将RTL寄存器与通用寄存器(GPRs)联系起来(很尴尬的是刚开始我并没有看到这段话T_T), 并提示我们使用union来实现。

源代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
typedef struct {

struct {
uint32_t _32;
uint16_t _16;
uint8_t _8[2];
} gpr[8];

/* Do NOT change the order of the GPRs' definitions. */

/* In NEMU, rtlreg_t is exactly uint32_t. This makes RTL instructions
in PA2 able to directly access these registers.
*/
rtlreg_t eax, ecx, edx, ebx, esp, ebp, esi, edi;

vaddr_t pc;
} x86_CPU_state;

那么有了提示思路就很明确, 我们只需要把gpr[]数组跟各个rtlreg共享内存位置即可。

于是我首先是这样构造:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
union {
struct {
uint32_t _32;
uint16_t _16;
uint8_t _8[2];
} gpr[8];

struct {
rtlreg_t eax;
rtlreg_t edx;
rtlreg_t ecx;
rtlreg_t ebx;
rtlreg_t ebp;
rtlreg_t esi;
rtlreg_t edi;
rtlreg_t esp;
};
};

很明显的,gpr[]数组的跟RTLREG结构体共享同一段地址,我们对gpr[]每个元素的修改也会直接影响到RTLREG的值。

但是,测试不通过

我很奇怪,但是找不到任何问题,在我不知道怎么修改的乱修改之后发现只要把gpr[]数组也改成union联合体就可以:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
union {
union {
uint32_t _32;
uint16_t _16;
uint8_t _8[2];
} gpr[8];

struct {
rtlreg_t eax;
rtlreg_t edx;
rtlreg_t ecx;
rtlreg_t ebx;
rtlreg_t ebp;
rtlreg_t esi;
rtlreg_t edi;
rtlreg_t esp;
};
};

从整体看,我并没有找到问题, 两段代码都是对rtlreg和gpr[]数组的内存地址统一,从功能上来看都是相同的。

答案就是, 在第一段代码中gpr[]是一个结构体代码,那么就意味着在结构体中的变量是一段连续内存地址中相互独立的:每个成员占有独立的内存空间, _32, _16, _8[2]都是独立的内存地址, 而这显然不符合寄存器的结构, 在i386手册中,通用寄存器是这么定义的:

PA1-i386通用寄存器内存地址

那么在第二段代码中_32, _16, _8[2]本质是共享了同一段内存地址,这意味着对其中任一成员的修改都会影响到同一内存位置的其他成员。

一些奇怪的问题

在上面图片中可以看到,RTLREG的顺序跟我的结构体中的顺序并不相同,但是却可以正常运行, 但是当我按照图片上的顺序修改之后却无法运行