1. 用C语言往内存写入
Naskfunc.nas中增加了一个函数可供C语言调用的函数_write_mem8
,用于实现直接写入指定内存地址的语句。
如果C语言中write_mem8(0x1234,0x56);
语句,
则动作上相当于汇编的MOV BYTE[0x1234],0x56
第一个数字在内存里的存放地址[ESP+4]
下一个数字的存放就依次累加4
值得注意的是,如果和C语言联合使用的话,有底寄存器能自由使用,有的寄存器不能自由使用。能自由使用的只有EAX、ECX、EDX这三个。至于其他寄存器,只能使用其值而不能改变其值。因为这些寄存器在C语言编译后生成的机器语言中,用于记忆非常重要的值。
这段代码中还增加了INSTRSET指令,是用来告诉nask这个程序是给486使用的,不然会被默认解释成8086机器使用(偶尔使用)的标签(label)或者常数。最后代码如下:
; 用彙編語言寫了一個名叫io_hlt的函數,因為之後要與bootpack.obj鏈接,所以也需要編譯成目標文件。因此將輸出格式設定為WCOFF模式,且設定成32位機器語言模式。
; naskfunc
; TAB=4
[FORMAT "WCOFF"] ; 製作目標文件的模式
[INSTRSET "i486p"] ; 告诉nask这个程序是给486使用的
[BITS 32] ; 製作32位模式用的機械語言
; 製作目標文件的信息
[FILE "naskfunc.nas"] ; 源文件名信息
GLOBAL _io_hlt,_write_mem8 ; 程序中包含的函數名
; 以下是實際的函數
[SECTION .text] ; 目標文件中寫了這些之後再寫程序
; C语言对汇编函数HLT的调用
_io_hlt: ; void io_hlt(void);
HLT
RET
; 写入指定内存地址的语句 C语言实现的汇编接口
; 这个函数类似于C语言中"write_mem8(0x1234,0x56);"语句,动作上相当于"MOV BYTE[0x1234],0x56"
_write_mem8: ;void write_mem8(int addr, int data);
MOV ECX,[ESP+4] ; [ESP+4]中存放的是地址,将其读入ECX
MOV AL,[ESP+8] ; [ESP+8]中存放的是数据,将其读入AL
MOV [ECX],AL
RET
话说[INSTRSET "i486p"]
这一句的添加位置作者没有详细说明,同时要记得在bootpack.c中的GLOBAL声明中加上新写的函数_write_mem8
如下:
GLOBAL _io_hlt,_write_mem8 ; 程序中包含的函數名
CPU家谱:
8086→80186→286(16)→386(32)→486→Pentium→PentiumPro→PentiumⅡ→PentiumⅢPentium4→……
然后修改bootpack.c里的代码:
/*在下面使用函数前需要先声明函数,相当于告訴C編譯器,有一個函數在別的文件里*/
void io_hlt(void);
void write_mem8(int addr, int data);
/*是函數聲明卻不用{ },而用;,這表示的意思是:函數在別的文件中,你自己找一下吧!*/
void HariMain(void)
{
int i; /*变量声明:i是一个32位整数*/
for (int i = 0xa0000; i <= 0xaffff; i++)
{
write_mem8(i, 15); /*MOV BYTE [i],15*/
}
for(;;) {
io_hlt(); /*執行naskfunc里的_io_hlt*/
}
}
2. 条纹图案
只需要在bootpack.c中修改写入值”15”为”i&0x0f”:
write_mem8(i, i & 0x0f);
对图形来说,0和1并不是作为数字来使用,重点是0和1 的排列方式。对于0和1的互相变化,有位运算”或”(OR)运算、”与”(AND)运算和”异或”(XOR)运算。
简单来说:
1 | 2 | 3 |
---|---|---|
OR | 有1得1 | 如 0100 OR 0010 → 0110 |
AND(&) | 同1为1 | 如 0100 AND 1101 → 0100 |
XOR | 不同得1 | 如 1010 XOR 0010 → 1000 |
将写入内存的数值经过&之后每隔16个像素,色号就反复一次,屏幕就能显示条纹了。
这个效果有点丧病啊,表示眼睛已经花了= =、
3. 挑战指针
前面提到的“C语言中没有直接写入指定内存地址的语句”是因为C语言中有替代这种命令的语句,也就是使用指针。
指针符号是”*”,*p中的p是地址,而*p是p指向地址的内容。
使用*i = i * 0x0f
可直接将i*0x0f
写入i指向的内存地址中。
*i = i * 0x0f
对应汇编的MOV [i], ( i * 0x0f)
,但如果直接这样写就不清楚[i]到底是BYTE还是WORD还是DWORD。
由于MOV指令的两个对象必须是相同字节长度,即同类型(BYTE/WORD/DWORD),除非另一方是寄存器才可以省略。同理,在使用指针时需要事先声明它的类型,即指针所指向内容的类型。
char i是类似AL的1字节变量,short i是类似于AX的2字节变量,int i是类似于EAX的4字节变量。
char p ; / 用于BYTE类地址 * /
short p; / 用于 WORD 类 地 址* /
int p ; / 用于DWORD 类 地 址 * /
以上指针中的p都是4字节,因为p是用于记录地址的变量。在汇编语言中,地址也像ECX一样,用4字节的寄存器来指定,所以也是4字节。
p = i; /带入地址/
p = i & 0x0f; /这可以替代write_mem8(i, i&0x0f)*/
在执行make run之后出现了“warning: assignment makes pointer from integer without a