Qemu裸机仿真 - 如何查看UART输出?

10

问题:

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

背景:

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

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

我正在使用的 ELF 文件 (mm.elf) 执行一个简单的矩阵乘法操作,然后打印它成功或失败以及运行时间。 这个 ELF 是使用 Xilinx ARM 工具链编译的。 我正在将其用于软件故障注入。 目前我使用 GDB 请求应该打印的变量值。 但是,在故障注入的上下文中,打印可能出现许多问题,因此看到实际通过 UART 发送的内容会很好。

相关答案:

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

这里有一些我尝试过的建议,但它不适用于此问题,因为该问题是关于在主机终端窗口中获取 Linux 引导消息。

如何在没有操作系统的情况下运行程序?

这个不太相关,因为它仍然假定用户有某种引导加载程序。 尽管必须在所有应用程序可运行之前提供引导加载程序,但 Xilinx 提供了诸如boot.S之类的文件中的系统代码,然后将其编译为在main之前运行的代码,并包含在 ELF 文件中。

我尝试过的事情:

我尝试将每个参数添加到我的当前 Qemu 命令的末尾。以下是结果:

  • -serial mon:stdio
    • 使用标准输入输出,无其他设置。
  • -serial null -serial mon:stdio (因为Cortex-A9有两个UARTs)
    • 使用标准输入输出,其中一个串行设备设置为空。
  • 在上述两种情况下添加 -semihosting
    • 使用半主机模式。
  • -serial stdio
    • 不能使用多个字符设备的stdio 。
    • 无法将串行设备连接到字符后端的 "stdio"。
  • -console=/dev/tty
    • 无效选项。
  • -curses
    • 黑屏,没有输出。

调查

我查看了ELF文件的反汇编,并验证了写入UART消息的地址与Qemu设置期望的地址相同(info mtree)。基地址是0xe0000000,两个地方都一样。

目标

我想捕获发送到UART的消息的输出。如果通过重定向到标准输出来完成此操作,那就可以了。如果它通过TCP套接字进行,则也可以。故障注入设置使用Python,并且Qemu作为子进程运行,因此可以轻松获取来自其中任何一个源的输出。

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

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脚本进行控制。

我的答案同样包含:https://dev59.com/GZnga4cB1Zd3GeqPdspU#50981397,其中包含两个UART QEMU设置。顺便提一下,RPI的运行相同的代码,只有不同的UART地址,没有“引导加载程序”(除了RPI强制你在负载之前运行的任何不可避免的东西)。 - Ciro Santilli OurBigBook.com
1个回答

7

在尝试输出任何字符之前,您需要初始化UART。例如,通过使用稍微修改过的this program,可以正常工作UART0仿真:

/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
< p > 在进行修改后,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);

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

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

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

如果是这种情况,使用Angel/Semihosting接口就可以完成任务 - 我明白你可能已经尝试过这种方法。

例子:

// 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!

使用半主机接口可以让您读/写文件,读取用户输入,并使用一些可用于C或C ++的xUnit测试框架-例如,我已经成功地使用CppUnitQEMUSemihosting接口在多个场合。
希望这可以帮助您。

@thatjames:如果你认为我已经正确回答了你的问题,请随意接受这个答案——我花了几个小时来回答你的问题,主要是因为我觉得它很有趣/具有挑战性——clang 的事情似乎不是原始问题的一部分。 - Frant
@thatjames:rdimon规范文件的路径明显是错误的,正确的路径应该是/opt/arm/9/gcc-arm-9.2-2019.12-x86_64-arm-none-eabi/arm-none-eabi/lib/rdimon.specs,假设从arm获得的gcc-arm-9.2-2019.12-x86_64-arm-none-eabi工具链已经被提取到/opt目录下。 - Frant
根据这篇论文,看起来你可以使用clang来构建半托管应用程序。 - Frant
是的,我想我没有清楚地说明我使用的工具链。我尝试了你的一些建议,但没有看到任何明显的成功。可能是因为半主机接口没有被正确调用,因为没有系统调用。我会尝试你说的使用newlib库的方法。 - thatjames
@thatjames:谢谢你的更新,我很高兴它起作用了。 - Frant
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接