简体   繁体   English

使用 GCC 内联汇编和取立即值的指令

[英]Using GCC inline assembly with instructions that take immediate values

The problem问题

I'm working on a custom OS for an ARM Cortex-M3 processor.我正在为 ARM Cortex-M3 处理器开发自定义操作系统。 To interact with my kernel, user threads have to generate a SuperVisor Call (SVC) instruction (previously known as SWI, for SoftWare Interrupt).要与我的 kernel 交互,用户线程必须生成一个 SuperVisor Call (SVC) 指令(以前称为 SWI,用于软件中断)。 The definition of this instruction in the ARM ARM is: ARM ARM中这条指令的定义是:

在此处输入图像描述

Which means that the instruction requires an immediate argument, not a register value.这意味着该指令需要立即参数,而不是寄存器值。

This is making it difficult for me to architect my interface in a readable fashion.这让我很难以可读的方式构建我的界面。 It requires code like:它需要如下代码:

asm volatile( "svc #0");

when I'd much prefer something like当我更喜欢类似的东西

svc(SVC_YIELD);

However, I'm at a loss to construct this function, because the SVC instruciton requires an immediate argument and I can't provide that when the value is passed in through a register.但是,我无法构造这个 function,因为 SVC 指令需要立即参数,而当值通过寄存器传入时我无法提供。

The kernel: kernel:

For background, the svc instruction is decoded in the kernel as follows作为背景,svc指令在kernel中解码如下

#define SVC_YIELD   0
// Other SVC codes

// Called by the SVC interrupt handler (not shown)
void handleSVC(char code)
{
  switch (code) {

    case SVC_YIELD:
      svc_yield();
      break;
    // Other cases follow

This case statement is getting rapidly out of hand, but I see no way around this problem.这个案例陈述很快就失控了,但我看不出解决这个问题的办法。 Any suggestions are welcome.欢迎提出任何建议。

What I've tried我试过的

SVC with a register argument带有寄存器参数的 SVC

I initially considered我最初考虑过

__attribute__((naked)) svc(char code)
{
    asm volatile ("scv r0"); 
}

but that, of course, does not work as SVC requires a register argument.但这当然不起作用,因为 SVC 需要一个寄存器参数。

Brute force蛮力

The brute-force attempt to solve the problem looks like:解决问题的蛮力尝试如下所示:

void svc(char code)
  switch (code) {
    case 0:
      asm volatile("svc #0");
      break;
    case 1:
      asm volatile("svc #1");
      break;
    /* 253 cases omitted */
    case 255:
      asm volatile("svc #255");
      break;
  }
}

but that has a nasty code smell.但那有一股难闻的代码味道。 Surely this can be done better.这当然可以做得更好。

Generating the instruction encoding on the fly即时生成指令编码

A final attempt was to generate the instruction in RAM (the rest of the code is running from read-only Flash) and then run it:最后一次尝试是在 RAM 中生成指令(代码的 rest 从只读闪存运行)然后运行它:

void svc(char code)
{
  asm volatile (
      "orr r0, 0xDF00  \n\t" // Bitwise-OR the code with the SVC encoding
      "push {r1, r0}   \n\t" // Store the instruction to RAM (on the stack)
      "mov r0, sp      \n\t" // Copy the stack pointer to an ordinary register
      "add r0, #1      \n\t" // Add 1 to the address to specify THUMB mode
      "bx r0           \n\t" // Branch to newly created instruction
      "pop {r1, r0}    \n\t" // Restore the stack
      "bx lr           \n\t" // Return to caller
      );
}

but this just doesn't feel right either.但这也感觉不对。 Also, it doesn't work - There's something I'm doing wrong here;此外,它不起作用 - 我在这里做错了什么; perhaps my instruction isn't properly aligned or I haven't set up the processor to allow running code from RAM at this location.也许我的指令没有正确对齐,或者我没有设置处理器以允许在此位置从 RAM 运行代码。

What should I do?我应该怎么办?

I have to work on that last option.我必须处理最后一个选项。 But still, it feels like I ought to be able to do something like:但是,感觉我应该能够做类似的事情:

__attribute__((naked)) svc(char code)
{
    asm volatile ("scv %1"
         : /* No outputs */
         : "i" (code)    // Imaginary directive specifying an immediate argument
                         // as opposed to conventional "r"
          ); 
}

but I'm not finding any such option in the documentation and I'm at a loss to explain how such a feature would be implemented, so it probably doesn't exist.但是我在文档中没有找到任何这样的选项,而且我无法解释如何实现这样的功能,所以它可能不存在。 How should I do this?我应该怎么做?

You want to use a constraint to force the operand to be allocated as an 8-bit immediate. 您希望使用约束来强制将操作数分配为8位立即数。 For ARM, that is constraint I . 对于ARM,这是约束I So you want 所以你要

#define SVC(code) asm volatile ("svc %0" : : "I" (code) )

See the GCC documentation for a summary of what all the constaints are -- you need to look at the processor-specific notes to see the constraints for specific platforms. 有关所有constaints的摘要,请参阅GCC文档 - 您需要查看特定于处理器的注释以查看特定平台的约束。 In some cases, you may need to look at the .md (machine description) file for the architecture in the gcc source for full information. 在某些情况下,您可能需要查看gcc源代码中架构的.md (机器描述)文件以获取完整信息。

There's also some good ARM-specific gcc docs here . 还有一些不错的特定ARM-GCC文档在这里 A couple of pages down under the heading "Input and output operands" it provides a table of all the ARM constraints 在“输入和输出操作数”标题下面几页,它提供了所有ARM约束的表格

使用宏怎么样:

#define SVC(i)  asm volatile("svc #"#i)

As noted by Chris Dodd in the comments on the macro, it doesn't quite work, but this does: 正如Chris Dodd在关于宏的评论中所指出的那样,它并不是很有效,但是这样做:

#define STRINGIFY0(v) #v
#define STRINGIFY(v) STRINGIFY0(v)
#define SVC(i)  asm volatile("svc #" STRINGIFY(i))

Note however that it won't work if you pass an enum value to it, only a #defined one. 但是请注意,如果您将枚举值传递给它,它只会是#defined。

Therefore, Chris' answer above is the best, as it uses an immediate value, which is what's required, for thumb instructions at least. 因此,克里斯上面的答案是最好的,因为它使用立即值,这是所需要的,至少是拇指指令。

My solution ("Generating the instruction encoding on the fly"): 我的解决方案(“动态生成指令编码”):

#define INSTR_CODE_SVC      (0xDF00)
#define INSTR_CODE_BX_LR    (0x4770)

void svc_call(uint32_t svc_num)
{
    uint16_t instrs[2];

    instrs[0] = (uint16_t)(INSTR_CODE_SVC | svc_num);
    instrs[1] = (uint16_t)(INSTR_CODE_BX_LR);

    // PC = instrs (or 1 -> thumb mode)
    ((void(*)(void))((uint32_t)instrs | 1))();
}

It works and its much better than switch-case variant, which takes ~2kb ROM for 256 svc's. 它的工作原理和它比switch-case变体要好得多,后者需要大约2kb的ROM才能生成256个svc。 This func does not have to be placed in RAM section, FLASH is ok. 这个func不必放在RAM部分,FLASH就可以了。 You can use it if svc_num should be a runtime variable. 如果svc_num应该是运行时变量,则可以使用它。

As discussed in this question, the operand of SVC is fixed, that is it should be known to the preprocessor, and it is different from immediate Data-processing operands. 正如在这个问题中所讨论的那样, SVC的操作数是固定的,即预处理器应该知道它,它与立即数据处理操作数不同。

The gcc manual reads gcc手册上写着

'I'- Integer that is valid as an immediate operand in a data processing instruction . 'I'-作为数据处理指令中的立即操作数有效的整数。 That is, an integer in the range 0 to 255 rotated by a multiple of 2. 也就是说,0到255范围内的整数旋转2的倍数。

Therefore the answers here that use a macro are preferred, and the answer of Chris Dodd is not guaranteed to work, depending on the gcc version and optimization level. 因此,这里使用宏的答案是首选,并且Chris Dodd的答案不能保证可行,具体取决于gcc版本和优化级别。 See the discussion of the other question. 请参阅另一个问题的讨论。

I wrote one handler recently for my own toy OS on Cortex-M.我最近在 Cortex-M 上为我自己的玩具操作系统编写了一个处理程序。 Works if tasks use PSP pointer.如果任务使用 PSP 指针,则工作。

Idea: Get interrupted process's stack pointer, get process's stacked PC, it will have the instruction address of instruction after SVC, look up the immediate value in the instruction.思路:获取被中断进程的栈指针,获取进程的栈PC,就会有SVC之后指令的指令地址,查找指令中的立即数。 It's not as hard as it sounds.它并不像听起来那么难。

uint8_t __attribute__((naked)) get_svc_code(void){
__asm volatile("MSR R0, PSP"); //Get Process Stack Pointer (We're in SVC ISR, so currently MSP in use)
__asm volatile("ADD R0, #24"); //Pointer to stacked process's PC is in R0
__asm volatile("LDR R1, [R0]"); //Instruction Address after SVC is in R1
__asm volatile("SUB R1, R1, #2"); //Subtract 2 bytes from the address of the current instruction. Now R1 contains address of SVC instruction
__asm volatile("LDRB R0, [R1]"); //Load lower byte of 16-bit instruction into R0. It's immediate value.
//Value is in R0. Function can return
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM