Tag Archives: 技术

刺猬战争 Hedgewars

又大半年没有更新blog了…
昨天发现一个类似”百战天虫”的开源游戏Hedgewars, 支持很多操作系统 (windows/linux/BSD/…), 可以在http://www.hedgewars.org下载安装程序和源码 (Windows/Linux). 游戏里面的主角从虫子变成了刺猬 (单人模式里面貌似npc是水果人…), 也是一样的游戏套路, 满天乱扔东西…游戏是用C++和Pascal写成的, 用了Qt库和SDL (Simple DirectMedia Layer)系列库, 都是跨平台的东西.

下了一个0.9.9版, 在windows下面安装完之后, 运行没多久就出现一个错误说某个zh-CN.txt文件找不到…google一下, 删掉了locale目录下面所有的*zh-cn*文件, 游戏变成英文版, 不过能正常运行了. 总体来说感觉不错, 在我的破电脑上也能比较流畅的运行. 画面和百战天虫相比, 总体风格差不多, 一些细节还需要改进, 比如把炸弹扔到别人头上就看不见了…音乐还是有一定差距的, 不论是音质还是背景音乐的选择. 音效貌似是照搬百战天虫的音效. 操作方式和原来一样, 省的再去适应. 然后就是不仅支持网络对战, 竟然还有官方服务器…进去过后发现也有20多个人. orz…

作为一个爱岗敬业的IT民工, 体验一下安装版的游戏过后, 应该要自己build一个出来玩玩. 查了一下怎么build, 就只有在官网的FAQ里面有几句说明: 先下Qt, FreePascal, SDL系列库, 然后去源代码目录cmake + make搞定. 当然, 这是理想情况, 实际操作起来, 不会那么河蟹…我在windows下的步骤:

1) 先找到官网上列的库, 下载下来. 总共有200多MB的东西. 因为Qt很大 (100多MB), FreePascal也有35MB. 然后把安装程序都装上, 压缩包都解开. 安装Qt的时候会提示下载MinGW (GCC的windows移植版), 如果有, 就不必下了.

2) 设置PATH环境变量. 包括:
<CMake路径>bin – 能在cmd line直接下运行CMake
<MinGW路径>bin – 使CMake的选项-G “MinGW Makefiles”能工作
<Qt路径>bin – CMake Hedgewars的过程中需要用qmake等工具
<FreePascal路径>bini386-win32 – CMake Hedgewars的过程中需要用到FreePascal. 注意这个目录下面也有gcc的一系列工具, 所以最好把这个目录放到<MinGW路径>bin之后
<SDL路径>includeSDL – CMake Hedgewars的过程中需要SDL的头文件和库文件
<SDL_mixer路径>include – CMake Hedgewars的过程中需要SDL的头文件和库文件
<SDL路径>bin – (build生成的) Hedgewars.exe运行需要SDL的DLL
<SDL_mixer, SDL_net, SDL_ttf的路径>lib – 共3个路径, (build生成的) Hedgewars.exe运行需要的DLL

3) 进入cmd line到Hedgewars的源代码目录, 运行CMake . -G “MinGW Makefiles”, DONE. 选项-G指定生成makefile的generator. 这里用的MinGW.

第二步里加了很多路径到PATH中, 有三种类型的路径: 第一是为了使cmd line能找到并直接运行程序比如<CMake路径>bin; 第二是使CMake能找到需要的工具程序, 头文件和库文件比如<SDL_mixer路径>include (因为Hedgewars源代码里的的CMakeLists.txt文件包含一些搜索相应工具/库的”find_program”, “find_package”指令, 我不确定除了修改PATH以外还有其他什么更简单的方式让CMake能找到这些工具/库); 第三是使生成的Hedgewars.exe程序运行时能找到DLL比如<SDL路径>bin.

简单的引导程序

看了赵博的《Linux内核源代码完全注释》,读了潇前辈的《操作系统引导探究》《8259A保护模式下中断编程》,受益菲浅。想起N久前看到某期开发高手上的写自己的OS的文章,不由冲动起来,我要写一个~~~~~~引导程序:)。很简单的一个引导程序,仅仅打印几个字符而已,耗费了我几个小时,学到了很多东西。
首先建立实验环境。WinXP+VMWare(linux),用VMWare自带的模拟软驱的功能做了一个软驱镜像,ok了
然后用C写个工具,把引导程序写到软盘0扇区中。并标志最后两个字节为“55 AA”
在这篇日志里我并不打算详细说明系统启动和内存分布情况,强烈推荐《操作系统引导探究》,顺便勘误,图十中的引导区应为0x07c00到0x07dff,而原图误为到0x07cff :) 。同时补充一点,在IBM PC机中,中断向量表中每个表项占4字节,前两字节是中断处理程序段地址,另两字节存储偏移地址。另,参考了唐朔飞老师《计算机组成原理》一书,在硬向量方式中,IBM PC机中的中断表和潇前辈提到的JMP式中断表是很普遍的两种方式。只不过PC机采用前者(清华大学《IBM-PC汇编语言程序设计》)

Ok了,进入正题。先把C代码贴出来。写入引导扇区的工具

/* the program is used to write a boot program to the boot section of
the floopy
*/

#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include<stdio.h>

main(int argc,char* argv[])
{
int bootp,flp; /* the bootp point to the boot program,flp point to the flooy */
char buf[512]; /* data buffer */
if(argc==1)
{
printf(“Use the command like this: writetoboot bootfilenamen”);
return;
}

if((bootp=open(argv[1],O_RDONLY))==-1)
printf(“Boot file not existn”);
read(bootp,buf,510);
close(bootp);

buf[511]=0×55;
buf[512]=0xaa;

if((flp=open(“/dev/fd0″,O_WRONLY))==-1)
printf(“Read the floopy driver error,please check your diskn”);
write(flp,buf,512);
close(flp);
}

这个程序应该不难理解,主要运用了linux系统调用open read write。不明白的可以man一下看看。

接着是引导程序。最早的原始程序如下:

.text

entry start
start:
mov ax,cs !ffmsg是显示的欢迎信息,这个字符串放在了.text段里面。
mov es,ax

mov cx,#20
mov bp,#ffmsg
mov bx,#0×0007
mov ax,#0×1301
int 0×10
loop1:
jmp loop1

ffmsg:
.byte 13,10
.ascii “YY,I love you…”
.byte 13,10

用这个作为引导程序,结果是能引导(0扇区结尾是55 AA的都能引导-_-b),但是显示不出文字。这个程序主要是利用了0×10号bios中断调用。显示字符串。其中字符串地址为es:bp 而这个程序之所以错就是es段寄存器值错误。我原来以为ffmsg既然定义在.text段里,那么其段基址肯定就是cs代码段寄存器的值了,偏移地址可以通过ffmsg本身的名字获得,把cs赋给es就ok了。这样想的错误在于弄混了概念。程序中的段与段寄存器不是那么直观地对应的。
cs段寄存器和IP程序基数器联用,确定下一条指令的位置。但cs不一定指向存储这个程序的第一个内存单元。因此把cs的值赋给es的值显然是错误的。ffmsg只能表示待显示字符串首字符的偏移地址,在es地址错误的情况下。整体就错误了。
在这个基础上修改程序。很简单,系统引导的时候,引导程序会被加载到物理地址0x07c00处,所以把刚才的mov ax,cs指令换为mov ax,#0x07c00就成功了。这时es段寄存器指向了这个程序的首地址,bp指向正确的偏移地址。所以显示成功。
那么进一步考虑。CS寄存器的地址是多少呢?写程序验证

.text

entry start
start:
! yes,it works,but I want to see what is the value of CS
mov ax,#0x07c0
mov es,ax

mov cx,#20
mov bp,#ffmsg
mov bx,#0×0007
mov ax,#0×1301
int 0×10

mov dx,cs !从这开始,把cs段的值赋给dx寄存器。然后显示
add dh,#65
add dl,#65
mov ax,#0xb800
mov es,ax
seg es
mov [0],dh
seg es
mov [1],#0x1f
seg es
mov [2],dl
seg es
mov [3],#0x1f
loop1:
jmp loop1

ffmsg:
.byte 13,10
.ascii “YY,I love you…”
.byte 13,10

把cs段的值赋给dx寄存器。分别把低16位(dl)与高16位(dh)加上65(字符A的ascii码),然后送往显存(0xb8000处开始),打印出来,0x1f显示属性表示蓝底白字。这时候引导我们可以很清楚的看到,打印了两个A字符,说明cs段值为0。现在可以充分证明最早程序的错误所在。再进一步可以打印出IP的值,IP寄存器不可以直接访问,不过我们可以利用call指令使IP入栈的特性来取得IP。如下所示

.text

entry start
start:
! yes,it works,but I want to see what is the value of CS and IP
mov ax,#0x07c0
mov es,ax

mov cx,#20
mov bp,#ffmsg
mov bx,#0×0007
mov ax,#0×1301
int 0×10

mov dx,cs
add dh,#65
add dl,#65
mov ax,#0×9000 !把IP的值赋给BX寄存器
mov ss,ax !Set the stack sec pointer
call next1
next1:
pop bx !bx is the value of IP reg

mov ax,#0xb800
mov es,ax
seg es
mov [0],bh
seg es
mov [1],#0x1f
seg es
mov [2],bl
seg es
mov [3],#0x1f
loop1:
jmp loop1

ffmsg:
.byte 13,10
.ascii “YY,I love you…”
.byte 13,10

这次分别打印出bx寄存器的高位和低位,可以发现打印出了’|’与’#’两个字符,查一查对应的ascii码表,可以看出执行到next1时ip寄存器值为0x7c23。说明我们程序确实是从0x7c00处开始执行的。

as86 man手册

as86的资料实在是少之又少。翻译了man文档….不过翻译的很烂,就当练英文水平吧:) Linus为什么要用它来写boot程序呢,nnd..翻译真是一项辛苦的工作啊…特别是对翻译的东西还不了解的时候….

as86(1)

名称

as86 – as86-8086..80386处理器的汇编程序

概要格式

as86 [-0123agjuw] [-lm[list]] [-n name] [-o obj] [-b[bin]] [-s sym]
[-t textseg] src

as86_encap prog.s prog.v [prefix_] [as86 options]

描述

as86是8086..80386处理器下的汇编程序,它所采用的语法与Intel/MS采取的语法类似,而不同于广泛运用于UNIX下的汇编语法(译注,gas中的语法,AT&T汇编)

命令行中的src参数可为’-',代表对标准输入进行汇编。

as86_encap是一个脚本,使用了as86汇编程序,并且把生成的二进制文件转为一个C文件prog.v,用于被连接或者包含到程序里,例如引导块安装程序。prefix_参数定义一个加到源文件中所有定义的变量的前缀,缺省前缀是源文件名。…

选项

-0 以16位代码段运行,当使用了高于8086指令集的指令时警告。

-1 以16位代码段运行,当使用了高于80186指令集的指令时警告。

-2 以16位代码段运行,当使用了高于80286指令集的指令时警告。

-3 以32位代码段运行,不对任何指令发出警告信息(就算使用了486或586的指令)

-a 使汇编程序部分兼容于Minix asld.交换了[]与()的用法,并且改变了一些16位跳转与调用的语法(“jmp @(bx)” 就成了一个合法的指令)

-g 仅仅把global符号写入目标或者符号文件中

-j 把所有短跳转指令(译注:8位跳转称为短跳转)换成相似的16位或者32位跳转。并且把16位条件转移指令换为一个条件短转移命令与一个无条件长跳转组合

-O 汇编程序会做几遍额外的工作,以尝试支持向前引用。最多30遍。不推荐使用

-l 产生清单文件(list file),文件名写在选项后

-m 把宏展开后写在清单文件里

-n 把模块名写在选项之后(目标模块,而非源文件)

-o 生成目标文件,文件名写在选项之后

-b 生成纯二进制文件,文件名写在后面。这是一个没有头部的纯二进制文件(译注:类似Dos下的com和sys)如果没有-s选项程序将会在内存地址0处开始执行

-s 生成一个ASCII码符号文件,文件名写在选项后。很简单就能将其转换,用于与-b选项生成的二进制文件相关联和封装。如果二进制文件不从地址0处开始执行。那么符号文件表中前两项分别代表起始地址与结束地址

-u 假定未定义符号在未指定的段中被导入了

-w- 允许汇编程序输出警告信息

-t n 把所有text段的数据放到段n+3中.

AS86 资料

特殊字符

* 本行起始地址

;或! 注释起始符,另外,在一行起始处的“unexpected”字符被认为是注释(但是仍然会被显示在终端上)

$ 16进制数的前缀, C风格的前缀, 比如0×1234, 也可以使用.

% 2进制数的前缀.

# 立即数的前缀.

[ ] 间接寻址运算符.

与MASM不同,汇编程序没有标识符的类型信息,每个标识符仅仅代表是一个段地址和偏移地址。[]与立即数操作与传统汇编程序一致

例:

mov ax,bx
jmp bx
寄存器寻址, jmp指令把bx寄存器中的值拷到程序计数器中

mov ax,[bx]
jmp [bx]
简单的寄存器间接寻址, jmp指令把bx寄存器值指向的内存单元的值拷到程序计数器中

mov ax,#1234
立即数, 把1234赋值给ax寄存器

mov ax,1234
mov ax,_hello
mov ax,[_hello]
直接寻址,内存地址1234处的存储字赋给ax寄存器。注意第三个指令并不十分严格,只是为了与asld保持兼容所以保留(译注:若想将_hello标识符表示的值作为立即数使用,需要加上#前缀 #_hello)

mov ax,_table[bx]
mov ax,_table[bx+si]
mov eax,_table[ebx*4]
mov ax,[bx+_table]
mov ax,[bx+si+_table]
mov eax,[ebx*4+_table]
变址寻址。两种形式都可以,但是我认为第一种要更正确些,但是我往往用第二种形式:)

条件判断

IF, ELSE, ELSEIF, ENDIF

数字比较

IFC, ELSEIFC

字符串比较 (str1,str2)

FAIL .FAIL

生成用户错误

段相关

.TEXT .ROM .DATA .BSS
设置当前段。可以在前面加上关键字.SECT

LOC 数字表示段 0=TEXT, 3=DATA,ROM,BSS, 14=MAX. 连接器设定的段顺序现在是0,4,5,6,7,8,9,A,B,C,D,E,1,2,3.段 0 以及所有3以上的段都假设为text段。注意64K限制对3-14的段不适用。

标识符类型定义

EXPORT PUBLIC .DEFINE
导出符号

ENTRY 强制连接器在a.out文件里包含这个特殊符号

.GLOBL .GLOBAL
将一个标识符定义为外部的,并且强制就算不使用,也必须导入

EXTRN EXTERN IMPORT .EXTERN
导入外部标识符列表

NB: bin格式的文件不支持外部变量(译注:关于这些格式,推荐参考一下NASM的手册。纯C论坛上有中文的NASM手册)

.ENTER 标识出旧式bin格式(obs)的程序入口

数据定义

DB .DATA1 .BYTE FCB
1字节的对象列表

DW .DATA2 .SHORT FDB .WORD
2字节的对象列表

DD .DATA4 .LONG
4字节的对象列表

.ASCII FCC
写到输出的Ascii码字符串.

.ASCIZ Ascii 写到输出的Ascii码字符串,末尾添加nul

空间定义

.BLKB RMB .SPACE
以字节为单位计算空间

.BLKW .ZEROW
以字为单位计算空间 (一字2字节)

COMM .COMM LCOMM .LCOMM
通用数据域定义

其他实用伪指令

.ALIGN .EVEN
对齐

EQU 定义标识符(译注:可参考NASM或者MASM的EQU)

SET 定义可重定义的标识符

ORG .ORG
定义汇编位置(译注:即设置地址计数器的值,建议参考MASM的资料)

BLOCK 定义汇编位置并且把原来的汇编位置入栈

ENDB 回到刚才栈里记录的汇编位置

GET INCLUDE
插入新文件 (no quotes on name)

USE16 [cpu]
定义默认操作数大小为16位,参数表示程序代码将会运行在什么样的CPU的(86,186, 286,386,486,586)指令集上.使用了指定指令集之上的指令会产生警告信息

USE32 [cpu]
定义默认操作数大小为32位,参数表示程序代码将会运行在什么样的CPU的(86,186, 286,386,486,586)指令集上.使用了指定指令集之上的指令会产生警告信息

END 标识出本文件停止汇编的地方

.WARN 警告信息开关

.LIST 清单 on/off (1,-1)

.MACLIST
宏清单 on/off (1,-1)

宏的使用形式如下

MACRO sax
mov ax,#?1
MEND
sax(1)

未实现/未使用的

IDENT Define object identity string.

SETDP Set DP value on 6809

MAP Set binary symbol table map number.

寄存器

BP BX DI SI
EAX EBP EBX ECX EDI EDX ESI ESP
AX CX DX SP
AH AL BH BL CH CL DH DL
CS DS ES FS GS SS
CR0 CR2 CR3 DR0 DR1 DR2 DR3 DR6 DR7
TR3 TR4 TR5 TR6 TR7 ST

操作数类型说明

BYTE DWORD FWORD FAR PTR PWORD QWORD TBYTE WORD NEAR

near和far关键字并没有提供段间寻址编程的能力,所有”far”操作都是
都是通过显式地使用以下指令得到的:指令: jmpi, jmpf, callf, retf,
等等. Near关键字可以被用来强制使用80386的16位条件跳转指令.
‘Dword’和’word’ 能控制远跳转和远调用的操作数的大小

普通指令

这些指令和其他8086汇编程序所提供的指令大体上差不多,(译注:后面的
看不明白了.我的英语功底啊~555) the main exceptions being a few ‘
Bcc’ (BCC, BNE, BGE, etc) instructions which are shorthands f
or a short branch plus a long jump and ‘BR’ which is the longest
unconditional jump (16 or 32 bit).

长分支

BCC BCS BEQ BGE BGT BHI BHIS BLE BLO BLOS BLT BMI BNE BPC BPL
BPS BVC BVS BR

段间操作

CALLI CALLF JMPI JMPF

段修饰符指令

ESEG FSEG GSEG SSEG

字节操作指令

ADCB ADDB ANDB CMPB DECB DIVB IDIVB IMULB INB INCB MOVB MULB
NEGB NOTB ORB OUTB RCLB RCRB ROLB RORB SALB SARB SHLB SHRB SBBB
SUBB TESTB XCHGB XORB

标准指令

AAA AAD AAM AAS ADC ADD AND ARPL BOUND BSF BSR BSWAP BT BTC BTR
BTS CALL CBW CDQ CLC CLD CLI CLTS CMC CMP CMPS CMPSB CMPSD CMPSW
CMPW CMPXCHG CSEG CWD CWDE DAA DAS DEC DIV DSEG ENTER HLT IDIV
IMUL IN INC INS INSB INSD INSW INT INTO INVD INVLPG INW IRET
IRETD J JA JAE JB JBE JC JCXE JCXZ JE JECXE JECXZ JG JGE JL JLE
JMP JNA JNAE JNB JNBE JNC JNE JNG JNGE JNL JNLE JNO JNP JNS JNZ
JO JP JPE JPO JS JZ LAHF LAR LDS LEA LEAVE LES LFS LGDT LGS LIDT
LLDT LMSW LOCK LODB LODS LODSB LODSD LODSW LODW LOOP LOOPE
LOOPNE LOOPNZ LOOPZ LSL LSS LTR MOV MOVS MOVSB MOVSD MOVSW MOVSX
MOVW MOVZX MUL NEG NOP NOT OR OUT OUTS OUTSB OUTSD OUTSW OUTW
POP POPA POPAD POPF POPFD PUSH PUSHA PUSHAD PUSHF PUSHFD RCL RCR
REP REPE REPNE REPNZ REPZ RET RETF RETI ROL ROR SAHF SAL SAR SBB
SCAB SCAS SCASB SCASD SCASW SCAW SEG SETA SETAE SETB SETBE SETC
SETE SETG SETGE SETL SETLE SETNA SETNAE SETNB SETNBE SETNC SETNE
SETNG SETNGE SETNL SETNLE SETNO SETNP SETNS SETNZ SETO SETP
SETPE SETPO SETS SETZ SGDT SHL SHLD SHR SHRD SIDT SLDT SMSW STC
STD STI STOB STOS STOSB STOSD STOSW STOW STR SUB TEST VERR VERW
WAIT WBINVD XADD XCHG XLAT XLATB XOR

浮点

F2XM1 FABS FADD FADDP FBLD FBSTP FCHS FCLEX FCOM FCOMP FCOMPP
FCOS FDECSTP FDISI FDIV FDIVP FDIVR FDIVRP FENI FFREE FIADD
FICOM FICOMP FIDIV FIDIVR FILD FIMUL FINCSTP FINIT FIST FISTP
FISUB FISUBR FLD FLD1 FLDL2E FLDL2T FLDCW FLDENV FLDLG2 FLDLN2
FLDPI FLDZ FMUL FMULP FNCLEX FNDISI FNENI FNINIT FNOP FNSAVE
FNSTCW FNSTENV FNSTSW FPATAN FPREM FPREM1 FPTAN FRNDINT FRSTOR
FSAVE FSCALE FSETPM FSIN FSINCOS FSQRT FST FSTCW FSTENV FSTP
FSTSW FSUB FSUBP FSUBR FSUBRP FTST FUCOM FUCOMP FUCOMPP FWAIT
FXAM FXCH FXTRACT FYL2X FYL2XP1