chaos-mesh on mips64

2021-02-22  本文已影响0人  sarto

问题起因

编译chaos-mesh v1.0.3 报错

[root@localhost chaos-mesh]# make chaosdaemon
GO15VENDOREXPERIMENT="1" CGO_ENABLED=1 GOOS="" GOARCH="" go build -ldflags '-s -w -X 'github.com/chaos-mesh/chaos-mesh/pkg/version.buildDate=2021-02-20T03:20:41Z' -X 'github.com/chaos-mesh/chaos-mesh/pkg/version.gitCommit=28de66e58ef60056b1f1966527fe85cc2396d670' -X 'github.com/chaos-mesh/chaos-mesh/pkg/version.gitVersion=chart-0.3.3'' -o bin/chaos-daemon ./cmd/chaos-daemon/main.go


# github.com/chaos-mesh/chaos-mesh/pkg/ptrace
pkg/ptrace/ptrace_linux.go:210:61: p.backupRegs.Rip undefined (type *syscall.PtraceRegs has no field or method Rip)
pkg/ptrace/ptrace_linux.go:225:61: p.backupRegs.Rip undefined (type *syscall.PtraceRegs has no field or method Rip)
pkg/ptrace/ptrace_linux.go:261:6: regs.Rax undefined (type syscall.PtraceRegs has no field or method Rax)
pkg/ptrace/ptrace_linux.go:265:8: regs.Rdi undefined (type syscall.PtraceRegs has no field or method Rdi)
pkg/ptrace/ptrace_linux.go:267:8: regs.Rsi undefined (type syscall.PtraceRegs has no field or method Rsi)
pkg/ptrace/ptrace_linux.go:269:8: regs.Rdx undefined (type syscall.PtraceRegs has no field or method Rdx)
pkg/ptrace/ptrace_linux.go:271:8: regs.R10 undefined (type syscall.PtraceRegs has no field or method R10)
pkg/ptrace/ptrace_linux.go:273:8: regs.R8 undefined (type syscall.PtraceRegs has no field or method R8)
pkg/ptrace/ptrace_linux.go:275:8: regs.R9 undefined (type syscall.PtraceRegs has no field or method R9)
pkg/ptrace/ptrace_linux.go:289:61: p.backupRegs.Rip undefined (type *syscall.PtraceRegs has no field or method Rip)
pkg/ptrace/ptrace_linux.go:289:61: too many errors

查看对应的目录

root@sjt-pc:/wk/github.com/chaos-mesh/chaos-mesh/pkg/ptrace# tree
.
├── cwrapper_linux.go
├── ptrace_linux.go
└── ptrace_linux_test.go

参照 golang 的编译约束,这些文件仅在 linux 下生效,但是很明显在 mips 下报错了,说明该文件并不仅仅只有操作系统约束,还应该是架构约束,查看最新版本的 chaos-mesh 对应目录,果然已经加上了架构约束。

root@sjt-pc:/wk/github.com/chaos-mesh/chaos-mesh/pkg/ptrace# tree
.
├── cwrapper_linux.go
├── ptrace_linux_amd64.go
└── ptrace_linux_test.go

那么我们的问题就是写出 ptrace_linux_mips64le.go。

定位

以下分析仍然针对v1.0.3 版本,分析源码,找到这个文件中的与架构有关的部分,我将其分为四个部分。

1. Ptrace 系统调用相关寄存器

我们找到两段代码,可以看到 backupRegs 实际上是 *syscall.PtraceRegs。并且使用了其中的几个变量例如 Rip,Rax 等等。我们需要比对一下 amd64 和 mips64le 对这二者的实现有什么区别。

func (p *TracedProgram) Syscall(number uint64, args ...uint64) (uint64, error) {
    ...
    var regs syscall.PtraceRegs
    regs.Rax = number
    for index, arg := range args {
        // All these registers are hard coded for x86 platform
        if index == 0 {
            regs.Rdi = arg
        } else if index == 1 {
            regs.Rsi = arg
        } else if index == 2 {
            regs.Rdx = arg
        } else if index == 3 {
            regs.R10 = arg
        } else if index == 4 {
            regs.R8 = arg
        } else if index == 5 {
            regs.R9 = arg
        } else {
            return 0, fmt.Errorf("too many arguments for a syscall")
        }
    }
...
    return regs.Rax, p.Restore()
}

2. PC 寄存器

func (p *TracedProgram) Restore() error {
    err := syscall.PtraceSetRegs(p.pid, p.backupRegs)
    if err != nil {
        return errors.WithStack(err)
    }

    _, err = syscall.PtracePokeData(p.pid, uintptr(p.backupRegs.Rip), p.backupCode)
    if err != nil {
        return errors.WithStack(err)
    }

    return nil
}

3. 两个系统调用号

func (p *TracedProgram) WriteSlice(addr uint64, buffer []byte) error {
    ...
    // process_vm_writev syscall number is 311
    _, _, errno := syscall.Syscall6(311, uintptr(p.pid), uintptr(unsafe.Pointer(&localIov)), uintptr(1), uintptr(unsafe.Pointer(&remoteIov)), uintptr(1), uintptr(0))
    ...
}

func (p *TracedProgram) ReadSlice(addr uint64, size uint64) (*[]byte, error) {
    ...
    // process_vm_readv syscall number is 310
    _, _, errno := syscall.Syscall6(310, uintptr(p.pid), uintptr(unsafe.Pointer(&localIov)), uintptr(1), uintptr(unsafe.Pointer(&remoteIov)), uintptr(1), uintptr(0))
    ...
}

4. 两个指令的机器码

func (p *TracedProgram) JumpToFakeFunc(originAddr uint64, targetAddr uint64) error {
    instructions := make([]byte, 16)

    // mov rax, targetAddr;
    // jmp rax ;
    instructions[0] = 0x48
    instructions[1] = 0xb8
    binary.LittleEndian.PutUint64(instructions[2:10], targetAddr)
    instructions[10] = 0xff
    instructions[11] = 0xe0

    return p.PtraceWriteSlice(originAddr, instructions)
}

解决第一个问题,系统调用涉及的寄存器

首先在 golang 源码中分别找到 amd64 和 mips64le 对 syscall.PtraceRegs 的定义,结合 Ptrace 的知识,这里是用户空间中可以访问的寄存器信息。

ztypes_linux_amd64.go

type PtraceRegs struct {
    R15      uint64
    R14      uint64
    R13      uint64
    R12      uint64
    Rbp      uint64
    Rbx      uint64
    R11      uint64
    R10      uint64
    R9       uint64
    R8       uint64
    Rax      uint64
    Rcx      uint64
    Rdx      uint64
    Rsi      uint64
    Rdi      uint64
    Orig_rax uint64
    Rip      uint64
    Cs       uint64
    Eflags   uint64
    Rsp      uint64
    Ss       uint64
    Fs_base  uint64
    Gs_base  uint64
    Ds       uint64
    Es       uint64
    Fs       uint64
    Gs       uint64
}

ztypes_linux_mips64le.go

type PtraceRegs struct {
    Regs        [102]uint64
    U_tsize     uint64
    U_dsize     uint64
    U_ssize     uint64
    Start_code  uint64
    Start_data  uint64
    Start_stack uint64
    Signal      int64
    U_ar0       uint64
    Magic       uint64
    U_comm      [32]int8
}

结合系统调用的知识,在 amd64 下,系统调用就是将系统调用号放在 rax 寄存器中,然后将参数放在的 Rdi,Rsi,Rdx,R10,R8,R9 中,最后将返回值放在 rax 中。这是系统调用时参数传递的固定寄存器。
那么我们就要知道在 mips64le 下,系统调用是怎么工作的。我们可以在 go 代码中找到系统调用 plan9 的汇编实现。从实现中,我们可以看到,mips64le 系统调用的参数传递使用了 R4-R9 这几个寄存器,同时调用号和返回值使用了 R2。

src/syscall/asm_linux_amd64.s

TEXT ·Syscall6(SB),NOSPLIT,$0-80
    CALL    runtime·entersyscall(SB)
    MOVQ    a1+8(FP), DI
    MOVQ    a2+16(FP), SI
    MOVQ    a3+24(FP), DX
    MOVQ    a4+32(FP), R10
    MOVQ    a5+40(FP), R8
    MOVQ    a6+48(FP), R9
    MOVQ    trap+0(FP), AX  // syscall entry
    SYSCALL
    CMPQ    AX, $0xfffffffffffff001
    JLS ok6
    MOVQ    $-1, r1+56(FP)
    MOVQ    $0, r2+64(FP)
    NEGQ    AX
    MOVQ    AX, err+72(FP)
    CALL    runtime·exitsyscall(SB)
    RET
ok6:
    MOVQ    AX, r1+56(FP)
    MOVQ    DX, r2+64(FP)
    MOVQ    $0, err+72(FP)
    CALL    runtime·exitsyscall(SB)
    RET

同理,我们可以找到mips64对于 syscall 的实现

src/syscall/asm_linux_mips64.s

TEXT ·Syscall6(SB),NOSPLIT,$0-80
    JAL runtime·entersyscall(SB)
    MOVV    a1+8(FP), R4
    MOVV    a2+16(FP), R5
    MOVV    a3+24(FP), R6
    MOVV    a4+32(FP), R7
    MOVV    a5+40(FP), R8
    MOVV    a6+48(FP), R9
    MOVV    trap+0(FP), R2  // syscall entry
    SYSCALL
    BEQ R7, ok6
    MOVV    $-1, R1
    MOVV    R1, r1+56(FP)   // r1
    MOVV    R0, r2+64(FP)   // r2
    MOVV    R2, err+72(FP)  // errno
    JAL runtime·exitsyscall(SB)
    RET
ok6:
    MOVV    R2, r1+56(FP)   // r1
    MOVV    R3, r2+64(FP)   // r2
    MOVV    R0, err+72(FP)  // errno
    JAL runtime·exitsyscall(SB)
    RET

此时,我们便找到了在 mips64 下 syscall 使用的几个寄存器了,就是四号到九号寄存器 R4-R9。

第二个问题,解决 PC 寄存器

rip 寄存器即 ip 寄存器,指令指针寄存器,用于记录下一个指令的地址。在 amd64 下,其直接写明了 RIP 寄存器。所以需要找到 mips64le 的指令指针寄存器。
恰好在 src/syscall/syscall_linux_*.go 下,go 为我们提供了各个架构的 PC 寄存器的定义。

root@sjt-pc:/wk/github.com/golang/go# grep -rn "SetPC"
src/syscall/syscall_linux_mipsx.go:212:func (r *PtraceRegs) SetPC(pc uint64) { r.Regs[64] = uint32(pc) }
src/syscall/syscall_linux_386.go:376:func (r *PtraceRegs) SetPC(pc uint64) { r.Eip = int32(pc) }
src/syscall/syscall_linux_amd64.go:141:func (r *PtraceRegs) SetPC(pc uint64) { r.Rip = pc }
src/syscall/syscall_linux_riscv64.go:178:func (r *PtraceRegs) SetPC(pc uint64) { r.Pc = pc }
src/syscall/syscall_linux_mips64x.go:202:func (r *PtraceRegs) SetPC(pc uint64) { r.Regs[64] = pc }
src/syscall/syscall_linux_arm.go:225:func (r *PtraceRegs) SetPC(pc uint64) { r.Uregs[15] = uint32(pc) }
src/syscall/syscall_linux_s390x.go:283:func (r *PtraceRegs) SetPC(pc uint64) { r.Psw.Addr = pc }
src/syscall/syscall_linux_ppc64x.go:110:func (r *PtraceRegs) SetPC(pc uint64) { r.Nip = pc }
src/syscall/syscall_linux_arm64.go:193:func (r *PtraceRegs) SetPC(pc uint64) { r.Pc = pc }

第三个问题,解决系统调用

mips64le 的操作系统的系统调用号定义在 unistd.h 中,找到对于系统调用功能的调用号。 即 4345 和 4346

#define __NR_process_vm_readv       (__NR_Linux + 345)
#define __NR_process_vm_writev      (__NR_Linux + 346)

第四个问题,解决两个指令的机器码

func (p *TracedProgram) JumpToFakeFunc(originAddr uint64, targetAddr uint64) error {
    instructions := make([]byte, 16)

    // mov rax, targetAddr;
    // jmp rax ;
    instructions[0] = 0x48
    instructions[1] = 0xb8
    binary.LittleEndian.PutUint64(instructions[2:10], targetAddr)
    instructions[10] = 0xff
    instructions[11] = 0xe0

    return p.PtraceWriteSlice(originAddr, instructions)
}

首先理解汇编的含义,即将一个4字节数放入 rax 寄存器,然后跳转到该寄存器值所指向的地址。那么我们需要找到 mips64 下对应的指令,或者说机器码。参考 mips 指令 https://www.cnblogs.com/CoBrAMG/p/9237609.html
我们可以使用 ld 指令加载一个64位数到寄存器中,然后使用 jr 跳转,如下。编译这段汇编代码,并查看其对应的机器码。可以看到 ld 指令被拆分成了好几条指令,因为 mips64 每条指令只有 32 位,所以将这个64位数分四次每次16位分别载入到64位寄存器中。

    ld $2, 0x1122334455667788
    jr $2


[root@localhost test]# objdump -d main.o
0000000000000000 <test>:
   0:   3c021122    lui v0,0x1122
   4:   34423344    ori v0,v0,0x3344
   8:   00021438    dsll    v0,v0,0x10
   c:   34425566    ori v0,v0,0x5566
  10:   00021438    dsll    v0,v0,0x10
  14:   dc427788    ld  v0,30600(v0)
  18:   00400008    jr  v0
  1c:   00000000    nop

按照这个逻辑写出对应的机器码

func (p *TracedProgram) JumpToFakeFunc(originAddr uint64, targetAddr uint64) error {
    instructions := make([]byte,28)
    var l1,l2,l3,l4 uint16

    l4 = uint16(targetAddr & 0xff)
    targetAddr = targetAddr >> 16

    l3 = uint16(targetAddr & 0xff)
    targetAddr = targetAddr >> 16

    l2 = uint16(targetAddr & 0xff)
    targetAddr = targetAddr >> 16

    l1 = uint16(targetAddr & 0xff)

    
    // lui v0,0x1122           
    instructions[0] = 0x3c     
    instructions[1] = 0x02
    binary.LittleEndian.PutUint16(instructions[2:4],l1)

    // ori v0,v0,0x3344        
    instructions[4] = 0x34
    instructions[5] = 0x42     
    binary.LittleEndian.PutUint16(instructions[6:8],l2)
    
    // dsll v0,v0,0x10         
    instructions[8] = 0x00
    instructions[9] = 0x02
    instructions[10] = 0x14    
    instructions[11] = 0x38    

    // ori v0,v0,0x5566        
    instructions[12] = 0x34    
    instructions[13] = 0x42
    binary.LittleEndian.PutUint16(instructions[14:16],l3)

    // dsll v0,v0,0x10
    instructions[16] = 0x00
    instructions[17] = 0x02
    instructions[18] = 0x14
    instructions[19] = 0x38

    // ld,v0,0x7788
    instructions[20] =  0xdc
    instructions[21] =  0x42
    binary.LittleEndian.PutUint16(instructions[22:24],l4)

    // jr v0
    instructions[24] = 0x00
    instructions[25] = 0x40
    instructions[26] = 0x00
    instructions[27] = 0x08

    return p.PtraceWriteSlice(originAddr, instructions)
}

上一篇下一篇

猜你喜欢

热点阅读