用龙芯EJTAG硬件断点优化Linux ptrace watch性能

在MIPS标准的协处理器0(CP0)中定义一组硬件watchpoints接口,由于某些原因,龙芯3系列处理器并未实现,这就导致了在该架构Linux系统中用gdb watch只能使用软件断点,真心非常、非常慢。:(

好消息是龙芯3系列处理器是实现了MIPS EJTAG的,兼容2.61标准,那么能否利用MIPS EJTAG的硬件断点功能部件实现Linux ptrace的watchpoints功能呢?答案是肯定的。让我们一起看看具体的方法吧。

首先,我们需要更改BIOS中的异常处理函数,将EJTAG调试异常重新路由至Linux内核中处理,因为MIPS EJTAG异常处理程序的入口地址固定为0xbfc00480

         /* Debug exception */
         .align  7           /* bfc00480 */
         .set    push
         .set    noreorder
         .set    arch=mips64r2
         dmtc0   k0, CP0_DESAVE
         mfc0    k0, CP0_DEBUG
         andi    k0, 0x2
         beqz    k0, 1f
          mfc0   k0, CP0_STATUS
         andi    k0, 0x18
         bnez    k0, 2f
          nop
 1:
         mfc0    k0, CP0_EBASE
         ins     k0, zero, 0, 12
         addiu   k0, 0x480
         jr      k0
          dmfc0  k0, CP0_DESAVE
 2:
         la      k0, 0xdeadbeef
         dmtc0   k0, CP0_DEPC
         dmfc0   k0, CP0_DESAVE
         deret
         .set    pop

这段处理程序实现了两个功能:
1. 将来自用户态的sdbbp指令触发的异常路由至地址 0xdeadbeef。
2. 将来自内核态的sdbbp指令触发的异常或是任意态的非sdbbp触发的异常路由至 ebase+0x480。

接着,我们还需要修改内核,实现下列功能:
1. 实现 EJTAG watch 相关的 probe、install、read、clear 等操作,及合适的调试异常处理程序。
2. 实现 Linux ptrace watch 接口与 EJTAG watch 的对接。

See: https://github.com/heiher/linux-stable/commits/ejtag-watch-4.9

Over!

MIPS J类指令目标范围

MIPS 跳转指令共分为三类:基于PC的相对跳转、基于PC区域的相对跳转、基于寄存器的绝对跳转。其中基于 PC 区域的相对跳转也就是我们要说的 J 类指令。

J类指令有长达26位的指令 index 编码域,因为指令都是4字节对齐的,所有表示的范围是 256M(28位)。那么J类跳转的目标地址是如何计算的呢?

目标PC = 延迟槽指令PC的28位以上的高位 || (J类指令26位的立即数 << 2)

是不是不易想像范围,看看图示吧:

j-class
 
|: 265M 边界
j: j 指令位置
t: 可行的跳转目标位置
 
----------------|--------------------------------|--------------------------------|----------------
---------------j|tttttttttttttttttttttttttttttttt|--------------------------------|----------------
----------------j-ttttttttttttttttttttttttttttttt|--------------------------------|----------------
----------------|j-tttttttttttttttttttttttttttttt|--------------------------------|----------------
----------------|tttttttttttttttj-ttttttttttttttt|--------------------------------|----------------
----------------|ttttttttttttttttttttttttttttttj-|--------------------------------|----------------
----------------|-------------------------------j|tttttttttttttttttttttttttttttttt|----------------
----------------|--------------------------------j-ttttttttttttttttttttttttttttttt|----------------

Over!

mips64el toolchain for x86_64

mips64el toolchain 是用于在 x86_64 平台交叉编译 mips64el 目标程序的工具集,该工具集分为两种大版本:odd-spreg 和 no-odd-spreg,其中龙芯仅适用 no-odd-spreg 版本。系统库包含 mips64el o32, n32 和 n64 多种版本的库,分别有依赖于 Linux 2.6 内核和 Linux 3.4 内核的两种版本。另外还有支持 x86_64 交叉编译 Mozilla JS 引擎的支持包。

下载
Source: mips64el-toolchain-2.src.tar.xz
no-odd-spreg
toolchain: mips64el-toolchain-2.x64.tar.xz
system libaries (Linux 2.6): mips64el-toolchain-linux-2.6-2.x64.tar.xz
system libaries (Linux 3.4): mips64el-toolchain-linux-3.4-2.x64.tar.xz
system libaries (Linux 3.4 MozJS): mips64el-toolchain-linux-3.4-mozjs-2.x64.tar.xz
odd-spreg
toolchain: mips64el-toolchain-2.x64.tar.xz
system libaries (Linux 2.6): mips64el-toolchain-linux-2.6-2.x64.tar.xz
system libaries (Linux 3.4): mips64el-toolchain-linux-3.4-2.x64.tar.xz

安装

sudo tar --numeric-owner -xf xxxx -C /

配置
设置环境变量

export PATH=${PATH}:/opt/mips64el-toolchain/bin

切换系统库

sudo ln -s -f linux-2.6 /opt/mips64el-toolchain/platforms/current

编译

# MIPS32 o32
mips64el-unknown-linux-gnu-gcc -march=mips32r2 -mabi=32 -o test test.c
# MIPS64 n32
mips64el-unknown-linux-gnu-gcc -march=mips64r2 -mabi=n32 -o test test.c
# MIPS64 n64
mips64el-unknown-linux-gnu-gcc -march=mips64r2 -mabi=64 -o test test.c

Over!

x86 pslldq to Loongson psllq

x86 pslldq 指令逻辑左移字节为单位的数据,而转换成龙芯的MMI只能使用 dsll 和 dsrl 指令模拟实现,需要特别注意的是 dsll 和 dsrl 指令移动的数据是以位为单位的。

/* SSE: pslldq (bytes) */
#define _mm_psllq(_D, _d, _s, _s64, _tf)                    \
        "subu %["#_tf"], %["#_s64"], %["#_s"] \n\t"         \
        "dsrl %["#_tf"], %["#_d"l], %["#_tf"] \n\t"         \
        "dsll %["#_D"h], %["#_d"h], %["#_s"] \n\t"          \
        "dsll %["#_D"l], %["#_d"l], %["#_s"] \n\t"          \
        "or %["#_D"h], %["#_D"h], %["#_tf"] \n\t"
pslldq $4, %xmm0 => mm_psllq(d, d, s32, s64, t)

Over!

看龙芯3A的 dmtc1 指令有多慢!

龙芯2F和3A处理器都实现了与 x86 MMX 基本兼容的 SIMD,即 MMI,该 ASE 是在浮点部件中的实现的,并且复用了 64-bit 的浮点寄存器(FPR)。在使用 MMI 时不可避免的会使用到通用寄存器向浮点器移动数据的情况,那么 dmtc1 的效率如何呢?

GPR 向 FPR 移动数据的指令共有3种:
mtc1 : 从 GPR 向 FPR 移动 32-bit 的数据,64-bit 平台上目标 FPR 的高 32-bit 清 0。
mthc1 : 从 GPR (低 32-bit)向 FPR 的高 32-bit 移动 32-bit 的数据,目标 FPR 的低 32-bit 数据保留。
dmtc1 : 从 GPR 向 FPR 移动 64-bit 数据。

从上面的说明可以看出, dmtc1 的功能是可以使用 mtc1 与 mthc1 模拟实现的,那么我们就设计个实验程序来验证一下这两条方式的时间开销分别如何吧。
程序的逻辑大致如下:

for (i=0; i<100000000; i++) {
#if 0
    move $2, $3
    mtc1 $3, $f31
    dsra $3, 32
    mthc1 $3, $f31
    move $3, $2
    ....
    ....
#else
    dmtc1 $3, $f31
    dmtc1 $3, $f31
     ....
     ....
#endif
}

结果:
在 MIPS64 系统上,每个循环中做8次GPR2FPR的数据移动,其 dmtc1 实现时间大概为 0m4.463s,而 mtc1 与 mthc1 组合实现为 0m3.857s,后者如不做寄存器的保存恢复,开销仅为 0m1.791s。

Over!

Firefox 30.0a1 for 龙芯3初步评测

经过一段时间的 OdinMonkey for MIPS N32 的移植,现在 Firefox 30.0a1 终于可以在龙芯3A笔记本上跑起来了,OdinMonkey for MIPS N32 基于 OdinMonkey for MIPS O32,这过程中感谢 Branislav Rankov 的热心帮助。初步跑了一下 Octane 2.0 性能评测,得分为 1010,比 Firefox 17.0.1 增加了 405 分。下一步的工作是针对龙芯平台的特定优化。

617834d12f2eb9385d05ca44d7628535e4dd6fbf

虽然 JavaScript 的性能有所提升了,但部分页面滚动的响应仍然不是太好,经简单的分析发现瓶颈有可能在 Xorg 上。

Over!

GDB 使用自定义命令实现一次执行多个命令

在调试 JIT 的过程中,经常要输出一些信息再跳过断点,使用 GDB 的自定义命令可以使工作变得简单。

(gdb) define mynext
Type commands for definition of "mynext".
End with a line saying just "end".
>i r
>x/8x $sp
>set $pc = $pc + 4
>c
>end
(gdb) mynext

Over!