用c语言实现内存写入
只显示黑乎乎的窗口一点意义也没有,我们需要值写入到现存中,以此来让显示器显示一些图像,首先利用汇编语言来定义一个函数,函数名称为_write_mem8,函数接收两个四字节的变量esp+4获取第一个变量的地址,esp+8获取第二个变量的地址,因为每个传过来的变量大小都是四字节,所有+ 4即可,然后将指定地址地址中保持到ecx中,将第二个变量保存到al中,将al值赋值到ecx中。
[INSTRSET 'i386p']表示汇编解析是利用486解析的,如果不指定的,默认为8086,8086使用的16位寄存器,没办法使用eax ecx esp这些32位寄存器。
[INSTRSET 'i386p']
GLOBAL _write_mem8
_write_mem8:
mov ecx, [esp+4]
mov al, [esp+8]
mov [ecx], al
ret
使用c语言,调用我们在汇编中定义的函数,循环在地址里赋值即可。
void io_hlt(void);
void write_mem8(int addr, int data);
void HariMain(void) {
int i = 0xa0000;
for (; i <= 0xaffff; i++) {
write_mem8(i, 15);
}
fin:
io_hlt();
goto fin;
}
这里直接使用指针不调用函数也是可以实现的。
void io_hlt(void);
void write_mem8(int addr, int data);
void HariMain(void) {
int *p;
int i = 0xa0000;
for (; i <= 0xaffff; i++) {
p = i;
*p = 15;
}
fin:
io_hlt();
goto fin;
}
运行结果如下,整个屏幕都会显示白色的,因为我们将显存地址全部写如了15,而15就代表白色。
条纹图案
以下语句也很好理解,就是循环地址为i的地方保存i与0x0f进行与运算值,将i和0xf转成二进制,两边同时进行与运算,如果两边为1结果为1,两边只要有一方为0,那么结果为0.
void io_hlt(void);
void write_mem8(int addr, int data);
void HariMain(void) {
int i = 0xa0000;
for (; i <= 0xaffff; i++) {
write_mem8(i, i & 0x0f);
}
fin:
io_hlt();
goto fin;
}
执行结果如下:
指针
指针上面例子以及演示过了,无非就是定义指针变量,将指针地址修改为i,然后直接操作指针地址的值为指定值。需要注意的是,以下汇编指令是没办法运行的
mov [0xf212], 21
原因是[0xf212]没有指定地址中存储的数据的自己大小是多少,这样的话将21保存到地址中就没办法保存了,因为21肯定是从地位赋值到高位的,字节大小不知道,肯定就没办法进行赋值了。
指针变量大小为四字节,因为指针变量中存储的就是32位地址,而32位地址刚好就是四字节,那么指针变量大小肯定就是四字节。
char是一字节,表示byte。
short是二字节,表示word。
int是字节表示,表示dword。
色号设定
之前调用bios中断0x19 al = 0x13 ah=00的画面设置为320*200 色号8位。
在8位色号中可以设置2^8=255个不同的颜色到调色板中。
void init_palette(void)
{
// vag调色盘最多只能设置16种不同的颜色
static unsigned char table_rgb[18 * 3] = {
0x00, 0x00, 0x00, /* 0:黑 */
0xff, 0x00, 0x00, /* 1:梁红 */
0x00, 0xff, 0x00, /* 2:亮绿 */
0xff, 0xff, 0x00, /* 3:亮黄 */
0x99, 0xdd, 0xcc, /* 4:淡蓝 */
0xff, 0x00, 0xff, /* 5:亮紫 */
0x00, 0xff, 0xff, /* 6:浅亮蓝 */
0xff, 0xff, 0xff, /* 7:白 */
0xc6, 0xc6, 0xc6, /* 8:亮灰 */
0x84, 0x00, 0x00, /* 9:暗红 */
0x00, 0x84, 0x00, /* 10:暗绿 */
0x84, 0x84, 0x00, /* 11:暗黄 */
0x00, 0x00, 0x84, /* 12:暗青 */
0x84, 0x00, 0x84, /* 13:暗紫 */
0x00, 0x84, 0x84, /* 14:浅暗蓝 */
0x84, 0x84, 0x84, /* 15:暗灰 */
0xb6, 0xa3, 0xbc, /* 16:dreamer鬃毛颜色 */
0xFF, 0xC0, 0xCB,
};
set_palette(0, 18, table_rgb);
return;
}
在定义RGB色号需要占用三个字节,书中如果不使用static进行赋值的话,占用内存是48 * 3 * 3个字节数,不知道是怎么得出来的,我觉得使用static和使用普通变量赋值的字节数都是相同的。。大概。上面定义了17个颜色数据,17 * 3 = 51个字节。使用static修饰的遍历在编译期就已经赋值了,
在c语言中如下定义static变量
static char a = 0x7f
那么在汇编中就会以以下方式进行赋值
a:
db 0x7f
普通局部变量存储在栈空间中的,函数调用完毕就会被释放掉了,而使用static修饰的变量是存储在静态存储区的,即便是函数执行完毕,变量依旧不会被释放。
如下所示,每次调用static都会修改变量内容
普通变量则是分配在栈中,每次调用函数都会重新分配
接着说设置调色盘的函数,如果cpu只连接内存而不连接其他设备的话,那么这台电脑只能由计算和存储功能,联网、展示图片什么的功能都没有,我们的电脑肯定不是这样的,因此cpu除了需要与存储设备连接还与其他设备连接,设备与cpu交互通过设备编码来进行交互,例如下面代码的0x03c9和0x03c8,cpu向设备发信息被称为out,cpu拉取设备发送的信息被称为in。
VGA文档信息:https://wiki.osdev.org/VGA_Hardware#VGA_Registers
我们根据设备编码向指定的设备发消息便可以自定义我们的调色盘。如下图所示,0x03c8是我们设置调色盘需要的设备编码。
设置调色盘的流程:
- 关中断,为了避免在初始化调色盘的过程中被其他服务将程序中断,首先将中断进行屏蔽。
cli命令是关中断,将中断标志位设置成0. - 以R、G、B的顺序将颜色写入到0x03c8端口中,如下所示这个函数就是在端口中写入调试盘序列,也就是第一个。
io_out8(0x03c8, start);
接着按R、G、B的顺序将颜色写入到端口0x03c9中,如果想要接着继续设定调色盘,那么可以忽略0x03c8,直接设置0x03c9即可。
文档:https://moddingwiki.shikadi.net/wiki/VGA_Palette
io_out8(0x03c9, rgb[0] / 4);的原因是VGA红绿蓝三种颜色,每种颜色各占一个字节,且每个字节只能使用后低6位来表示颜色,值 / 4就相当于右移两位,虽然不知道为什么VGA只能使用6位来设置颜色,网上找资料也没找到,但是确实就是这么设置的。
如下FF二进制是 1111 1111
除4则是:
3. 设定调色盘完毕后打开中断。
sti命令是开中断,将中断标志位设置成1。当cpu遇到中断时会不会断下来是根据这个标志位来设定的。
io_load_eflags()函数的作用是在关中断前获取下中断标志位的值,在对调色盘设置完毕后再使用io_store_eflags()函数将中断位初始化回去。
void set_palette(int start, int end, unsigned char *rgb)
{
int i, eflags;
// 记录中断寄存器中的值
eflags = io_load_eflags();
// 关中断
io_cli();
io_out8(0x03c8, start);
for (i = start; i <= end; i++)
{
// 将调色板中记录的RGB值存储到0x03c9地址中
// 为什么要 / 4 rgb 红绿蓝(R、G、B)都是一个具有 6 位值(从 0 到 63)的字节+ 两个 0)
// 前两位0不识别 所以要右移两位使得前两位识别
io_out8(0x03c9, rgb[0] / 4);
io_out8(0x03c9, rgb[1] / 4);
io_out8(0x03c9, rgb[2] / 4);
rgb += 3;
}
// 开中断
io_store_eflags(eflags);
return;
}
PUSHFD是将标志位寄存器进行压栈
POPFD则是将标志位寄存器出栈
由于不能直接将标志位寄存器的值直接睡得着到eax寄存器中,所有需要先将标志位寄存器进行压栈,在出栈的时候在赋值给eax寄存器中。
stroe也是一样,先将eax寄存器进行压栈,出栈的时候再将标志位寄存器进行出栈操作。
;
_io_cli:
cli
ret
_io_out8:
mov edx, [esp+4]
mov al, [esp+8]
out dx, al
ret
_io_store_eflags:
mov eax, [esp + 4]
push eax
popfd
ret
_io_load_eflags:
pushfd
pop eax
ret
画矩形
之前设置的显示模式是320 * 200,横为320是像素,宽为200像素,左上角为0,0的像素的0,下一行1,0则是320,因为每一行320哥像素,Vrarm的算法就是0xa0000 + 320 * y + x。如下面这个方法。
void boxfill8(unsigned char *vram, int xSize, unsigned int color, int x0, int y0, int x1, int y1) {
int x, y;
for (x = x0; x <= x1; x++) {
for (y = y0; y <= y1; y++) {
*(vram + xSize * y + x) = color;
}
}
}
这个方法遍历x到x0,y到y0所有的像素点,并设置我们调色盘设置的指定颜色进行展示。
调用展示如下:
void HariMain(void)
{
int j = 0;
int i = 0xa0000;
init_palette();
char *p;
p = (char *) 0xa0000;
boxfill8(i, 320, 0xf, 70, 150, 120, 172);
fin:
io_hlt();
goto fin;
}
标签:语言,int,void,练习,0x00,0x84,画面,io,字节
From: https://www.cnblogs.com/lyraHeartstrings/p/17373069.html