机密计算 -> ARM CCA

简单介绍:
https://cloud.tencent.com/developer/article/2411953
https://blog.csdn.net/qq_43543209/article/details/135659463
https://blog.csdn.net/weixin_42135087/article/details/121174706
https://community.arm.com/management/archive/cn/b/blog/posts/arm-1203521971

开源项目:
https://github.com/caibirda/islet_runable

# 如果下载失败考虑网络问题
git config --global http.proxy http://192.168.101.11:7890
export https_proxy=http://192.168.101.11:7890
export http_proxy=http://192.168.101.11:7890
export all_proxy=socks5://192.168.101.11:7890

aarch64介绍:
https://blog.csdn.net/jimfire/category_11329148.html

sudo apt-get install qemu-user gcc-aarch64-linux-gnu
# .c
aarch64-linux-gnu-gcc -static -o hello hello.c
qemu-aarch64 ./hello
# .s
aarch64-linux-gnu-as -c first.s
aarch64-linux-gnu-gcc -static -o first first.o
# 检查返回值
echo $?

AArch64中有31个通用寄存器x0,x1,…x30,x31寄存器被称为xzr,也就是零寄存器。
为了提供32位的处理,我们可以使用Wn来代替Xn表示32位。
所以x6寄存器的低32位就是w6。也没必要声明高32位。xzr寄存器的32位等价寄存器是wzr。
当一个指令的目的寄存器是32位寄存器,那么高32位置为0。它们是不被保留的。

mov w0, #2           // w0  2
add w0, w1, wzr   // w0  w1 + 0
orr b0, b1, wzr
add w0, #1, w1   // ERROR: second operand should be an integer register
add w0, #1, #2   // ERROR: second operand should be an integer register.

main函数总是接受程序名为第一个参数。

// test.s
.text
.globl main

main:
  add w0, w0, #1   // w0 ← w0 + 1
  ret              // return from main
$ aarch64-linux-gnu-gcc -c test.s
$ aarch64-linux-gnu-gcc -o test test.o
$ ./test ; echo $?
2
$ ./test foo ; echo $?
3
$ ./test foo bar ; echo $?
4
reg, LSL, #amount
reg, LSR, #amount
reg, ASR, #amount
reg, ROR, #amount
add r1, r2, r3, LSL #4 /* r1  r2 + (r3 << 4)

一个扩展操作符以kxtw的形式,其中k是一个整型值,该值是我们想要扩展的宽度。w是窄的哪个值。
对于前者,整型的类型能够使U(unsigned)或者S(signed,例如,补码)。对于后者,宽度能够使B,H或者W,分别对应byte(寄存器的低8位),half-word(寄存器的低16位)或者word(寄存器的低32位)。
这也意味着扩展符有uxtb, sxtb, uxth, sxth, uxtw, sxtw。

add x0, x1, w2, sxtb // x0  x1 + ExtendSigned8To64(w2)
add w0, w1, w2, sxtb // w0  w1 + ExtendSigned8To32(w2)
add x2, x0, x1, sxtw #1   // x2  x0 + (ExtendSigned16To64(x1) << 1)
add x2, x0, x1, sxtw #2   // x2  x0 + (ExtendSigned16To64(x1) << 2)

mul Rdest, Rsource1, Rsource2 // Rdest  Rsource1 * Rsource2
umull Xdest, Wsource1, Wsource2  // Xdest  Wsource1 * Wsource2
smull Xdest, Wsource1, Wsource2  // Xdest  Wsource1 * Wsource2 
mul Xlower, Xsource1, Xsource2   // Xlower  Lower64Bits(Xsource1 * Xsource2)
smulh Xupper, Xsource1, Xsource2 // Xupper  Upper64Bits(Xsource1 * Xsource2)

udiv Rdest, Rsource1, Rsource2  // Rdest  Rsource1 / Rsource2
sdiv Rdest, Rsource1, Rsource2  // Rdest  Rsource1 / Rsource2

mvn Rdest, Rsource    // Rdest  ~Rsource

// and,orr和eor
and Rdest, Rsource1, #immediate          // Rdest ← Rsource1 & immediate
and Rdest, Rsource1, Rsource2            // Rdest  Rsource1 & Rsource2
and Xdest, Xsource1, Xsource2, shiftop   // Xdest  Xsource1 & shiftop(Xsource2)
and Wdest, Wsource1, Wsource2, shiftop   // Wdest  Wsource1 & shiftop(Wsource2)

// bic(位清空),orn(或非)和eon(异或非)
orn Rdest, Rsource1, Rsource2            // Rdest  Rsource1 | ~Rsource2
orn Xdest, Xsource1, Xsource2, shiftop   // Xdest  Xsource1 | ~shiftop(Xsource2)
orn Wdest, Wsource1, Wsource2, shiftop   // Wdest  Wsource1 | ~shiftop(Wsource2)
ldr W2, [X1]  // W2  *X1 (32-bit load)
ldr X2, [X1]  // X2  *X1 (64-bit load)

ldr W2, [X1, #4]        // W2  *(X1 + 4)   [32-bit load]
ldr W2, [X1, #-4]       // W2  *(X1 - 4)   [32-bit load]
ldr X2, [X1, #240]      // X2  *(X1 + 240) [64-bit load]
ldr X2, [X1, #400]      // X2  *(X1 + 400) [64-bit load]
// ldr X2, [X1, #404]   // Invalid offset, not multiple of 8!
// ldr X2, [X1, #-400]  // Invalid offset, must be positive!
// ldr X2, [X1, #32768] // Invalid offset, out of the range!

ldr W1, [X2, X3]  // W1  *(X2 + X3) [32-bit load]
ldr X1, [X2, X3]  // X1  *(X2 + X3) [64-bit load]

ldr W1, [X2, W3, sxtw] // W1  *(X2 + ExtendSigned32To64(W3))    [32-bit load]
ldr W1, [X2, W3, uxtw] // W1  *(X2 + ExtendUnsigned32To64(W3))  [64-bit load]

ldr W1, [X2, W3, sxtw #3] // W1  *(X2 + ExtendSigned32To64(W3 << 3)) [32-bit-load]

在AArch64中有2中索引模式:预先索引(pre-indexing)和事后索引(post-indexing)模式。在预先索引模式下,其基地址寄存器添加偏移计算地址,并且这个地址会写回基地址寄存器。在事后索引模式中,基地址被用于计算地址,但是在地址访问基地址寄存器后会更新地址的值,该值是添加了偏移的。

这两种方式看起来有点相似,都是更新用偏移基地址寄存器。它们不同之处在于偏移的计算时机:预先索引模式会在访问地址之前计算,事后索引模式会在访问之后计算。而我们能够使用的偏移值必须在-256到255之间。

ldr X1, [X2, #4]! // X1  *(X2 + 4)
                  // X2  X2 + 4
ldr X1, [X2], #4  // X1  *X2
                  // X2  X2 + 4
ldr Xn, addr_of_var // Xn  &var
// ldr Wn, addr_of_var // Wn  &var
... 
addr_of_var : .dword variable // This tells the assembler that
                              // we want here the address of var
                              // (This is not to be executed!)
... 
ldr Xm, [Xn]  // Xm  *Xn    [64-bit load]
ldr Wm, [Xn]  // Wm  *Xn    [32-bit load]

全局变量被定义在.data节。为了实现这个方法,我们只要简单地定义它们的初始值。如果我们想定义一个32位的变量,我们使用.word。如果我们想顶一个64位的变量,我们使用.dword。
在Linux中AArch64不需要内存访问对其。但是如果它们对齐了,则它们在硬件中会执行得快一点。所以我们使用.balign指令去按照数据得尺寸(以字节)对齐每个变量。

// globalvar.s
.data

.balign 8 // Align to 8 bytes
.byte 1
global_var64 : .dword 0x1234  // a 64-bit value of 0x1234
// alternatively: .word 0x1234, 0x0

.balign 4 // Align to 4 bytes
.byte 1
global_var32 : .word 0x5678   // a 32-bit value of 0

注意,有必要在最后的连接阶段使用-static标记。这将创建一个static文件,这个文件被直接加载到内存。默认地,程序运行的时候,链接器是创建动态文件,这些动态文件被动态链接器加载。动态链接器会在一个地址上加载程序,超过232个提交这些地址非法。当使用.dword,静态链接器保证了对动态链接器的声明是发射的,所以后者能够在运行时修复64位地址。

GDB调试

$ qemu-arm -g 12345 ./a.out &
$ gdb-multiarch ./a.out
(gdb) set arch arm
The target architecture is assumed to be mips
(gdb) set endian little
The target is assumed to be little endian
(gdb) target remote localhost:12345
Remote debugging using localhost:12345
0x00400280 in _ftext ()
(gdb) x/i $pc
  => 0x767cb880    move   $t9, $ra

show architecture 和 show endian 检查当前设置。 可使用专为 ARM 设计的 GDB 版本,如 arm-none-eabi-gdb。

// 无条件分支
/* branch */
.text
.globl main
main:
  mov w0, #3   // w0  3
  b jump       // branch to label jump
  mov w0, #4   // w0  4
  jump:
    ret        // end function

条件分支指令: b.cond
该指令在它的操作数上面有一点特殊,就是条件码,它是以指令名称的形式体现的。这个cond部分就是条件码,并且必须是上面所述的一部分。

  b.lt label1 // if w0 < 0 then branch to label1
  b.eq label2 // if w0 == 0 then branch to label2
  b.gt label3 // if w0 > 1 then branch to label3
label1:
  // code for label1
  b end_of_arithmetic_if // branch to end_of_arithmetic_if
label2:
  // code for label2
  b end_of_arithmetic_if // branch to end_of_arithmetic_if
label3:
  // code for label3
  b end_of_arithmetic_if // branch to end_of_arithmetic_if
end_of_arithmetic_if:
  // rest ouf our Fortran program :)

调用一个函数的核心是跳转分支,但是这是一个特殊的常用分支,因此,有必要为此单独设置一个指令。在AArch64中,这个指令就是bl,其意思是branch和link。它是一个无条件分支,该分支的作用是设置x30寄存器中的值是下一条指令的地址。回忆一下,x30是一个通用目的寄存器,但是在本例中,我们给予它特殊的意义:它包含了函数结束之后的地址。历史原因,当x30寄存器被用于此目的时,它被称为链接寄存器。

从函数返回后,唯一我们叫做的事情就是跳转到x30寄存器中的的地址。有一个指令能够无条件跳转到保存在寄存器中的地址,叫br。所以,调用一个函数和返回一个函数的过程如下。

.text
my_function:
  br x30
caller:
  bl my_function
  // more instructions ...

但是从一个函数返回是一个通用操作,所以,我们直接用ret,而不用br x30。

.text
my_function:
  ret
caller:
  bl my_function
  // more instructions ...

x0-x7被用来传递参数和返回值。这些寄存器的值可能会被调用函数自由修改(callee)所以调用者会忽略它们中的内容。即使它们不被用来传递参数和返回值。这也意味着它们在实际应用中是调用者保存寄存器。
x8-x18是对每个函数而言是临时寄存器。对于函数而言,它不管它们里面的值,因此,在实际过程中,它们是调用者保存寄存器。
x19-x28寄存器是被调用者保存寄存器,即它们被函数调用之前应保存,在返回之后应该恢复
我们已经知道x30是链接寄存器并且它的值必须被保存直到函数使用ret指令返回到调用者。

.data

.balign 8
/* This is the greeting message */
say_hello: .asciz "Hello world!"

.balign 8
/* We need to keep x30 otherwise we will not be able to return from main! */
keep_x30: .dword 0

.text

/* We are going to call a C-library puts function */
.globl puts

.globl main
main:
    ldr x0, addr_keep_x30     // w0  &keep_30   [64]
    str x30, [x0]             // *keep_30  x30  [64]

    ldr x0, addr_say_hello    // w0  &say_hello [64]
    bl puts                   // call puts

    ldr x0, addr_keep_x30     // w0  &keep_30   [64]
    ldr x30, [x0]             // x30  *keep_30  [64]

    mov w0, #0                // w0  0
    ret                       // return

addr_keep_x30 : .dword keep_x30
addr_say_hello: .dword say_hello
Licensed under CC BY-NC-SA 4.0