【问题标题】:What's the point of providing input and output operands if they are not specified in ASM template?如果 ASM 模板中没有指定输入和输出操作数,那么提供输入和输出操作数有什么意义?
【发布时间】:2020-06-02 15:14:59
【问题描述】:

我在u-boot/arch/arm/lib/semihosting.c 中找到了以下代码,它使用bkpt 和其他指令并提供输入和输出操作数,即使它们未在ASM 模板中指定:

static noinline long smh_trap(unsigned int sysnum, void *addr)
{
    register long result asm("r0");
#if defined(CONFIG_ARM64)
    asm volatile ("hlt #0xf000" : "=r" (result) : "0"(sysnum), "r"(addr));
#elif defined(CONFIG_CPU_V7M)
    asm volatile ("bkpt #0xAB" : "=r" (result) : "0"(sysnum), "r"(addr));
#else
    /* Note - untested placeholder */
    asm volatile ("svc #0x123456" : "=r" (result) : "0"(sysnum), "r"(addr));
#endif
    return result;
}

最小的、可验证的例子:

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
  register long result asm("r0");
  void *addr = 0;
  unsigned int sysnum = 0;
  __asm__ volatile ("bkpt #0xAB" : "=r" (result) : "0"(sysnum), "r"(addr));

  return EXIT_SUCCESS;
}

根据ARM架构参考手册bkpt指令 采用单个 imm 参数,根据我对GCC manual section on inline assembly 的阅读,GCC 不允许提供操作数,如果它们 模板中没有指定。使用-S 生成的输出程序集:

    .arch armv6
    .eabi_attribute 28, 1
    .eabi_attribute 20, 1
    .eabi_attribute 21, 1
    .eabi_attribute 23, 3
    .eabi_attribute 24, 1
    .eabi_attribute 25, 1
    .eabi_attribute 26, 2
    .eabi_attribute 30, 6
    .eabi_attribute 34, 1
    .eabi_attribute 18, 4
    .file   "bkpt-so.c"
    .text
    .align  2
    .global main
    .arch armv6
    .syntax unified
    .arm
    .fpu vfp
    .type   main, %function
main:
    @ args = 0, pretend = 0, frame = 8
    @ frame_needed = 1, uses_anonymous_args = 0
    @ link register save eliminated.
    str fp, [sp, #-4]!
    add fp, sp, #0
    sub sp, sp, #12
    mov r3, #0
    str r3, [fp, #-8]
    mov r3, #0
    str r3, [fp, #-12]
    ldr r2, [fp, #-12]
    ldr r3, [fp, #-8]
    mov r0, r2
    .syntax divided
@ 10 "bkpt-so.c" 1
    bkpt #0xAB
@ 0 "" 2
    .arm
    .syntax unified
    mov r3, #0
    mov r0, r3
    add sp, fp, #0
    @ sp needed
    ldr fp, [sp], #4
    bx  lr
    .size   main, .-main
    .ident  "GCC: (Raspbian 8.3.0-6+rpi1) 8.3.0"
    .section    .note.GNU-stack,"",%progbits

那么"=r" (result) : "0"(sysnum), "r"(addr) 在这行中有什么意义:

__asm__ volatile ("bkpt #0xAB" : "=r" (result) : "0"(sysnum), "r"(addr));

?

【问题讨论】:

  • 函数名包含trap ...所以这些很可能是某种系统调用。陷阱处理程序大概使用r0 来接收和返回值。
  • 我很难理解编译器应该从这一行生成哪些汇编指令。
  • @user1042840 你真的看过生成的程序集吗?
  • @ThomasJager:是的,我做了,粘贴了。它只显示 bkpt #0xAB 尽管例如如果 addr 改变了周围的线会改变。我最想知道的是 GCC 应该如何理解这样的行以及这种行为记录在哪里?

标签: assembly gcc arm inline-assembly semihosting


【解决方案1】:

尽管这段代码存在于像 U-BOOT 这样的知名项目中,但这并不能增强信心。该代码依赖于这样一个事实,即ABI (call standard)r0(参数1)、r1(参数2)、r2(参数3)和@ 987654331@(参数 4)。

表 6.1 总结了 ABI:

U-BOOT 代码所做的假设是,当生成内联汇编时,传递给r1 中的函数的addr 仍然是相同的值。我认为这很危险,因为即使使用简单的非内联函数 GCC 也不能保证这种行为。我的观点是这段代码很脆弱,尽管它可能从未出现过问题,但理论上它可以。依赖底层编译器代码生成行为不是一个好主意。

我认为写成这样会更好:

static noinline long smh_trap(unsigned int sysnum, void *addr)
{
    register long result asm("r0");
    register void *reg_r1 asm("r1") = addr;
#if defined(CONFIG_ARM64)
    asm volatile ("hlt #0xf000" : "=r" (result) : "0"(sysnum), "r"(reg_r1) : "memory");
#elif defined(CONFIG_CPU_V7M)
    asm volatile ("bkpt #0xAB" : "=r" (result) : "0"(sysnum), "r"(reg_r1) : "memory");
#else
    /* Note - untested placeholder */
    asm volatile ("svc #0x123456" : "=r" (result) : "0"(sysnum), "r"(reg_r1) : "memory");
#endif
    return result;
}

此代码通过变量 (reg_r1) 传递 addr,该变量将被放入寄存器 r1 以用于内联汇编约束。在更高的优化级别上,编译器不会使用额外的变量生成任何额外的代码。我还放置了一个memory clobber,因为在没有寄存器的情况下以这种方式通过寄存器传递内存地址不是一个好主意。如果有人要制作此函数的内联版本,则会出现问题。内存破坏器将确保在运行内联汇编之前将任何数据实现到内存中,并在必要时在之后重新加载。


至于"=r" (result) : "0"(sysnum), "r"(addr)做什么的问题是:

  • "=r"(result) 是一个输出约束,它告诉编译器在内联汇编完成后寄存器r0 中的值将被放入变量addr
  • "0"(sysnum) 是一个输入约束,它告诉编译器 sysnum 将通过与约束 0 相同的寄存器传递到内联汇编代码中(约束 0 使用寄存器 r0)。
  • "r"(addr)addr 传递给一个寄存器,并且假设它将在带有 U-BOOT 代码的 r1 中。在我的版本中,它是这样明确定义的。

关于扩展内联汇编的操作数和约束的信息可以在GCC documentation 中找到。您可以找到其他特定于机器的约束here

hltbkptsvc 都被用作系统调用,以通过调试器 (semihosting) 执行系统服务。您可以找到有关半主机 here 的更多文档。不同的 ARM 体系结构使用稍微不同的机制。半主机系统调用的约定是r0 包含系统调用号; r1 包含系统调用的第一个参数;系统调用在返回用户代码之前在r0 中放置一个返回值。

【讨论】:

  • 我觉得这应该在上游修复。既然你写了固定版本,你想把补丁一起发给他们吗?
  • @JosephSible-ReinstateMonica :可能是个好主意。我可以提交补丁。如果有人想制作这个函数的内联版本,我的版本也应该可以工作。我很好奇他们是否强迫这个 noinline 以避免编译优化错误。内联版本根本无法保证第二个参数在 r1 中。
【解决方案2】:

这些指令用于从用户空间代码调用内核(或管理程序)(即执行syscall)。它们导致 CPU 发出一个陷阱,该陷阱被内核拦截和处理。在预定义寄存器中传递的附加数据(在本例中为r0r1)保存内核陷阱处理程序的“参数”。

【讨论】:

  • r1 在哪里使用?我只看到r0
  • @user1042840 是的,我也没有看到。我的理解是,开发人员依赖编译器将其保存在其原始位置(根据 ABI 为 r1),但不要问我为什么他希望它能够工作......
  • 好的。我认为svc 用于执行系统调用。 bkpt 会生成 Vector 表中列出的哪些异常?
  • 好的,我知道了,它会生成 Prefetch abort。
猜你喜欢
  • 2016-06-30
  • 2012-01-23
  • 2014-10-07
  • 1970-01-01
  • 2011-05-13
  • 1970-01-01
  • 2014-09-14
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多