读《Professional Assembly Language》之移位之谜

Professional Assembly Lanauge

Professional Assembly Lanauge

今年是离开校园的第五年,这五年来我一直在从事应用软件的设计、开发工作,大部分时间是在与高级编程语言、设计模式、业务逻辑打交道。它们大多流于表面,久而久之,与技术底层疏远了,诸如计算机组成原理、汇编语言、编译原理、数据结构以及算法慢慢得生疏,时至今日,我已经弄不懂IA-32里有几类寄存器、间接寻址、词法分析是怎么一回事情了。五年是一个契机,趁着下一个五年开始之际,我计划用三个月至半年时想间,重新学习这些知识,以期达到巩固基础,厚积薄发的目的。

学习过程肯定会有问题,找不出问题的学生不是好学生。因此,我把遇到的问题和解决的方法记录下来,便形成了读书笔记。本篇博文便是我在阅读《Professional Assembly Language》一书时,所作的其中一篇读书笔记。《Professional Assembly Language》,中文译作《汇编语言程序设计》,是我学习汇编语言时选择的工具书,该书对于我这种已经有了高级语言的使用经验,又热衷Linux的人来说非常合适。

《Professional Assembly Language》在第八章第二节讲解了Shift Instruction,在谈到算数左移(SAL)和逻辑左移(SHL)时,作者列举了该指令的三种格式:

  1. sal destination
  2. sal %cl, destination
  3. sal shifter, destination

其中,第三种格式表述存不清楚,shifter可以是immediate value?是common register?是memory location?下面先用shifter是memory location类型来做个试验。

1
2
3
4
5
6
7
8
9
10
11
12
13
.section .data
val:
        .int 0x1
count:
        .int 0x21
.section .text
.global _start
_start:
        nop
        sall count, val
        movl $1, %eax
        movl $0, %ebx
        int $0x80

执行汇编器以后,得到错误如下:
shift_over_32.s: Assembler messages:
shift_over_32.s:11: Error: suffix or operands invalid for `sal’

shifter为immediate value类型再次试验:

1
2
3
4
5
6
7
8
9
10
11
.section .data
val:
        .int 0x1
.section .text
.global _start
_start:
        nop
        sall $0x21, val
        movl $1, %eax
        movl $0, %ebx
        int $0x80

这次汇编和链接都通过,说明第一个例子中,用memory location类型作为shifter是不对的。
这个问题追根究底,可以从《Intel 64 and IA-32 Architectures Software Developer’s Manual》上找到答案:

The destination operand can be a register or a memory location. The count operand can be an immediate value or the CL register.
— 4-356 Vol. 2B

也就是说count operand(即shifter)的类型可以是立即数或者CL寄存器,而destination operand则不能是立即数。

在手册中,还可以看到这么一句话:

The count is masked to 5 bits (or 6 bits if in 64-bit mode and REX.W is used). The count range is limited to 0 to 31 (or 63 if 64-bit mode and REX.W is used).

也就是说,移位操作最多只能移动31位,那么超过了31位会怎么样呢?
(gdb) print /t val
$1 = 1
(gdb) s
_start () at shift_over_32.s:9
9 movl $1, %eax

(gdb) print /t val
$2 = 10

上述代码使得数字1算术左移33位,而实际的效果是只1位。在此情况下,移动位数的低5位(32-bit模式下)才起作用,33对应的二进制的低5位是00001,那么也就是算数左移1位。

如果修改左移33位为31位,那么可以想象最低位上的数字将移动到最高位,下面的代码可以佐证。

1
2
3
4
5
6
7
8
9
10
11
.section .data
val:
        .int 0x1
.section .text
.global _start
_start:
        nop
        sall $0x1f, val
        movl $1, %eax
        movl $0, %ebx
        int $0x80

在GDB下,两次查看val的值,符合预期。
(gdb) print /t val
$10 = 1
(gdb) s
_start () at shift_over_32.s:9
9 movl $1, %eax

(gdb) print /t val
$11 = 10000000000000000000000000000000

Leave a comment

Your comment