Qemu 裸机仿真 - 如何查看 UART 输出? [英] Qemu baremetal emulation - how to view UART output?

查看:47
本文介绍了Qemu 裸机仿真 - 如何查看 UART 输出?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

如何从使用 Qemu 运行的裸机程序获取 UART 输出?

How do I get the UART output from a baremetal program run with Qemu?

这是我一直在使用的命令行调用:

Here is the command line invocation I have been using:

qemu-system-arm -M xilinx-zynq-a9 -cpu cortex-a9 -nographic -kernel $BUILD_DIR/mm.elf -m 512M -s -S

  • 使用机器xilinx-zynq-a9
  • 处理器 cortex-a9
  • 由于这是裸机,可执行文件是一个自包含的 ELF 文件
  • -m 512M 表示平台有 512 MiB 的 RAM
  • -s-gdb tcp::1234
  • 的快捷方式
  • -S 表示在启动时冻结 CPU
    • using machine xilinx-zynq-a9
    • processor cortex-a9
    • as this is baremetal, the executable is a self contained ELF file
    • -m 512M signifies the platform has 512 MiB of RAM
    • -s is a shortcut for -gdb tcp::1234
    • -S means freeze CPU at startup
    • 我正在使用的 ELF 文件(mm.elf)执行简单的矩阵乘法运算,然后打印它是成功还是失败,以及运行需要多长时间.ELF 是使用 Xilinx ARM 工具链编译的.我将此用于软件故障注入.目前我使用 GDB 来询问应该打印的变量的值.但是,由于在故障注入的情况下打印可能会出现很多问题,因此最好看看实际通过 UART 发送的内容.

      The ELF file I am using (mm.elf) performs a simple matrix multiply operation, and then prints whether it succeeded or failed, and how long it took to run. The ELF was compiled using the Xilinx ARM toolchain. I am using this for software fault injection. Currently I use GDB to ask for the values of the variables which are supposed to be printed. However, as there are many things which could go wrong with printing in the context of fault injection, it would be nice to see what is actually sent over UART.

      将 QEMU 窗口输出重定向到运行 qemu 的终端

      这有一些我尝试过的建议,但它不适用,因为问题是关于在主机终端窗口中获​​取 Linux 启动消息.

      This has some suggestions I tried, but it isn't applicable because the question was about getting the Linux boot messages in the host terminal window.

      如何在没有操作系统?

      这不是很相关,因为它仍然假设用户有某种引导加载程序.虽然在技术上必须有一个引导加载程序才能运行应用程序,但赛灵思在 boot.S,然后作为代码编译到ELF文件中,在main之前运行.

      This is one isn't very related because it still assumes that the user has a bootloader of some kind. While there must technically be a bootloader for the application to run at all, Xilinx provides this system code in files like boot.S, which are then compiled into the ELF file as code which runs before main.

      我尝试将这些添加到当前 Qemu 命令的末尾.结果按照试过的参数.

      I tried adding each of these onto the end of my current Qemu command. The results follow the parameters tried.

      • -serial mon:stdio
        • 没什么
        • 没什么
        • 没什么
        • 无法通过多个字符设备使用 stdio
        • 无法将串行设备连接到字符后端stdio"
        • 无效选项
        • 黑屏,无输出

        我查看了 ELF 文件的反汇编,并验证了写入 UART 消息的地址与 Qemu 设置所期望的地址相同 (info mtree).基地址为0xe0000000,两处相同.

        I looked at the disassembly of the ELF file and verified that the address to which the the UART messages are being written is the same as the Qemu setup expects (info mtree). Base address is 0xe0000000, the same in both places.

        我希望能够捕获发送到 UART 的消息的输出.如果这是通过重定向到标准输出来完成的,那很好.如果它通过 TCP 套接字,那也没关系.故障注入设置使用 Python,并且 Qemu 作为子进程运行,因此很容易从其中任一来源获取输出.

        I want to be able to capture the output of messages sent to UART. If this is done by redirecting to stdout, that's fine. If it goes through a TCP socket, that's fine too. The fault injection setup uses Python, and Qemu is running as a subprocess, so it would be easy to get the output from either one of those sources.

        注意:在故障注入设置中运行时,Qemu 调用是

        Note: when run in the fault injection setup, the Qemu invocation is

        qemu-system-arm -M xilinx-zynq-a9 -cpu cortex-a9 -nographic -kernel $BUILD_DIR/mm.elf -m 512M -gdb tcp::3345 -S -monitor telnet::3347,server,nowait
        

        主要区别在于 1) GDB 端口号不同(因此多个实例可以同时运行)和 2) Qemu 将使用套接字上的 telnet 连接进行控制,因此它可以由 Python 脚本控制.

        The main differences being 1) the GDB port number is different (so multiple instances can run simultaneously) and 2) Qemu is to be controlled using a telnet connection over a socket, so it can be controlled by the Python script.

        推荐答案

        在尝试输出任何字符之前,您需要初始化 UART.UART0 仿真工作正常,例如使用稍微修改过的 这个程序:

        You need to initialize the UART prior to attempt outputing any characters. The UART0 emulation is working fine for example by using a slightly modified version of this program:

        /opt/qemu-4.2.0/bin/qemu-system-arm -semihosting --semihosting-config enable=on,target=native -nographic -serial mon:stdio -machine xilinx-zynq-a9 -m 768M -cpu cortex-a9 -kernel hello05.elf
        
        Hello number 1
        

        修改后git diff命令的输出为:

        diff --git a/Hello01/Makefile b/Hello01/Makefile
        index 4a1b512..8d6d12a 100644
        --- a/Hello01/Makefile
        +++ b/Hello01/Makefile
        @@ -1,10 +1,10 @@
         ARMGNU ?= arm-linux-gnueabihf
        -COPS    =   
        +COPS    = -g -O0  
         ARCH    = -mcpu=cortex-a9 -mfpu=vfpv3 
        
         gcc : hello01.bin
        
        -all : gcc clang
        +all : gcc 
        
         clean :
            rm -f *.o
        @@ -15,8 +15,6 @@ clean :
            rm -f *.img
            rm -f *.bc
        
        -clang: hello02.bin
        -
         startup.o : startup.s
            $(ARMGNU)-as $(ARCH) startup.s -o startup.o
        
        diff --git a/Hello01/hello01.c b/Hello01/hello01.c
        index 20cb4a4..14ed2a0 100644
        --- a/Hello01/hello01.c
        +++ b/Hello01/hello01.c
        @@ -10,16 +10,16 @@
         */
        
        
        -#define UART1_BASE 0xe0001000
        -#define UART1_TxRxFIFO0 ((unsigned int *) (UART1_BASE + 0x30))
        +#define UART0_BASE 0xe0000000
        +#define UART0_TxRxFIFO0 ((unsigned int *) (UART0_BASE + 0x30))
        
        -volatile unsigned int * const TxRxUART1 = UART1_TxRxFIFO0;
        +volatile unsigned int * const TxRxUART0 = UART0_TxRxFIFO0;
        
         void print_uart1(const char *s) 
         {
             while(*s != '\0') 
             {     /* Loop until end of string */
        -    *TxRxUART1 = (unsigned int)(*s); /* Transmit char */
        +    *TxRxUART0 = (unsigned int)(*s); /* Transmit char */
             s++; /* Next char */
             }
         }
        @@ -28,4 +28,4 @@ void c_entry()
         {
            print_uart1("\r\nHello world!");
            while(1) ; /*dont exit the program*/
        -}
        \ No newline at end of file
        +}
        diff --git a/Hello05/Makefile b/Hello05/Makefile
        index 9d3ca23..bc9bb61 100644
        --- a/Hello05/Makefile
        +++ b/Hello05/Makefile
        @@ -1,5 +1,5 @@
         ARMGNU ?= arm-linux-gnueabihf
        -COPS    =   
        +COPS    =  -g -O0
         ARCH    = -mcpu=cortex-a9 -mfpu=vfpv3 
        
         gcc : hello05.bin
        diff --git a/Hello05/hello05.c b/Hello05/hello05.c
        index 1b92dde..01ce7ee 100644
        --- a/Hello05/hello05.c
        +++ b/Hello05/hello05.c
        @@ -26,7 +26,7 @@
        
         void c_entry() 
         {
        -   init_uart1_RxTx_115200_8N1();
        +   init_uart0_RxTx_115200_8N1();
            printf("\nHello number %d\n",1);
            while(1) ; /*dont exit the program*/
         }
        diff --git a/Hello05/xuartps.c b/Hello05/xuartps.c
        index bdf7ad1..74f68bd 100644
        --- a/Hello05/xuartps.c
        +++ b/Hello05/xuartps.c
        @@ -16,42 +16,42 @@
         void putc(int *p ,char c);
        
         /*
        -* Initiate UART1  ( /dev/ttyACM0 on host computer )
        +* Initiate UART0  ( /dev/ttyACM0 on host computer )
         *   115,200 Baud 8-bit No-Parity 1-stop-bit
         */
        -void init_uart1_RxTx_115200_8N1()
        +void init_uart0_RxTx_115200_8N1()
         {
           /* Disable the transmitter and receiver before writing to the Baud Rate Generator */
        -  UART1->control_reg0=0; 
        +  UART0->control_reg0=0; 
        
           /* Set Baudrate to 115,200 Baud */
        -  UART1->baud_rate_divider =XUARTPS_BDIV_CD_115200;
        -  UART1->baud_rate_gen=     XUARTPS_BRGR_CD_115200;
        +  UART0->baud_rate_divider =XUARTPS_BDIV_CD_115200;
        +  UART0->baud_rate_gen=     XUARTPS_BRGR_CD_115200;
        
           /*Set 8-bit NoParity 1-StopBit*/
        -  UART1->mode_reg0   =   XUARTPS_MR_PAR_NONE;  
        +  UART0->mode_reg0   =   XUARTPS_MR_PAR_NONE;  
        
           /*Enable Rx & Tx*/
        -  UART1->control_reg0=   XUARTPS_CR_TXEN | XUARTPS_CR_RXEN | XUARTPS_CR_TXRES | XUARTPS_CR_RXRES ;      
        +  UART0->control_reg0=   XUARTPS_CR_TXEN | XUARTPS_CR_RXEN | XUARTPS_CR_TXRES | XUARTPS_CR_RXRES ;      
        
        
         }
        
        -void sendUART1char(char s)
        +void sendUART0char(char s)
         {
           /*Make sure that the uart is ready for new char's before continuing*/
        -  while ((( UART1->channel_sts_reg0 ) & UART_STS_TXFULL) > 0) ;
        +  while ((( UART0->channel_sts_reg0 ) & UART_STS_TXFULL) > 0) ;
        
           /* Loop until end of string */
        -  UART1->tx_rx_fifo= (unsigned int) s; /* Transmit char */
        +  UART0->tx_rx_fifo= (unsigned int) s; /* Transmit char */
         }
        
         /* "print.h" uses this function for is's printf implementation */
         void putchar(char c)
         {
           if(c=='\n')
        -    sendUART1char('\r');
        -  sendUART1char(c);
        +    sendUART0char('\r');
        +  sendUART0char(c);
         }
        
         /* <stdio.h>'s printf uses puts to send chars
        @@ -61,9 +61,9 @@ int puts(const char *s)
             while(*s != '\0') 
             { 
              if(*s=='\n')
        -         sendUART1char('\r');
        +         sendUART0char('\r');
        
        -      sendUART1char(*s); /*Send char to the UART1*/       
        +      sendUART0char(*s); /*Send char to the UART0*/       
               s++; /* Next char */
             }
             return 0;
        diff --git a/Hello05/xuartps.h b/Hello05/xuartps.h
        index fc5008f..64e3b88 100644
        --- a/Hello05/xuartps.h
        +++ b/Hello05/xuartps.h
        @@ -13,7 +13,7 @@
            #define u32 unsigned int
         #endif
        
        -#define UART1_BASE 0xe0001000
        +#define UART0_BASE 0xe0000000
         // Register Description as found in
         //    B.33 UART Controller (UART) p.1626
         struct XUARTPS{
        @@ -34,7 +34,7 @@ struct XUARTPS{
                 u32 Flow_delay_reg0;            /* Flow Control Delay Register  def=0*/
                 u32 Tx_FIFO_trigger_level;};    /* Transmitter FIFO Trigger Level Register */
        
        -static struct XUARTPS *UART1=(struct XUARTPS*) UART1_BASE;        
        +static struct XUARTPS *UART0=(struct XUARTPS*) UART0_BASE;        
        
         /*
             Page 496
        @@ -87,11 +87,11 @@ static struct XUARTPS *UART1=(struct XUARTPS*) UART1_BASE;
         #define XUARTPS_MR_CLKS_REF_CLK 0       /*  0: clock source is uart_ref_clk*/
        
         /*
        -* Initiate UART1  ( /dev/ttyACM0 on host computer )
        +* Initiate UART0  ( /dev/ttyACM0 on host computer )
         *   115,200 Baud 8-bit No-Parity 1-stop-bit
         */
        -void init_uart1_RxTx_115200_8N1();
        -void sendUART1char(char s);
        +void init_uart0_RxTx_115200_8N1();
        +void sendUART0char(char s);
         int puts(const char *s);
         //void putc((void*), char);
        

        ZedBoard-BareMetal-Examples/Hello05 目录执行的用于构建修改后的 Hello05 示例的命令是:

        The command executed from the ZedBoard-BareMetal-Examples/Hello05 directory for building the modified Hello05 example was:

        make ARMGNU=/opt/arm/9/gcc-arm-9.2-2019.12-x86_64-arm-none-eabi/bin/arm-none-eabi clean all 
        

        话虽如此,您的上一篇文章的最后一条评论 让我觉得您可能只想看到程序的输出,但不一定要使用 UART0.

        This being said, the last comment from your previous post made me think that you may just want to be able to see the output of your program, but not necessarily by using UART0.

        如果是这种情况,使用 Angel/Semihosting 界面就可以了工作 - 我知道你可能试图这样做.

        If this is the case, using the Angel/Semihosting interface would do the job - I understand you may have attempted to go this way.

        示例:

        // hello.c:
        
        #include <stdlib.h>
        
        int main(int argc, char** argv)
        {
            printf("Hello, World!\n");
            return EXIT_SUCCESS;
        }
        

        gcc 命令:

        /opt/arm/9/gcc-arm-9.2-2019.12-x86_64-arm-none-eabi/bin/arm-none-eabi-gcc -g -O0 --specs=rdimon.specs -o hello.elf hello.c
        

        qemu 命令:

        /opt/qemu-4.2.0/bin/qemu-system-arm -semihosting --semihosting-config enable=on,target=native -nographic -serial mon:stdio -machine xilinx-zynq-a9 -m 768M -cpu cortex-a9 -kernel hello.elf
        

        结果:

        Hello, World!
        

        使用半主机接口将允许您读/写文件、读取用户输入并使用一些xUnit 可用于 C 或 C++ 的测试框架 - 例如,我已经成功地使用了 CppUnit 带有 QEMU半主机接口. 多次.

        Using the semihosting interface would allow you to read/write files, read user input, and to use some of the xUnit testing frameworks available for either C or C++ - I have been for example successfully be using CppUnit with QEMU and the Semihosting interface. at several occasions.

        我希望这会有所帮助.

        这篇关于Qemu 裸机仿真 - 如何查看 UART 输出?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆