【问题标题】:Qemu baremetal emulation - how to view UART output?Qemu 裸机仿真 - 如何查看 UART 输出?
【发布时间】:2020-06-18 11:56:58
【问题描述】:

问题:

如何从使用 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 工具链编译的。我将它用于软件fault injection。目前我使用 GDB 来询问应该打印的变量的值。但是,由于在故障注入的上下文中打印可能会出错,因此很高兴看到实际通过 UART 发送的内容。

相关答案:

redirect QEMU window output to terminal running qemu

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

How to run a program without an operating system?

这不是很相关,因为它仍然假定用户具有某种引导加载程序。虽然从技术上讲,应用程序必须有一个引导加载程序才能运行,但赛灵思在 boot.S 等文件中提供此系统代码,然后将其编译为 ELF 文件,作为在 main 之前运行的代码。

我尝试过的事情:

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

  • -serial mon:stdio
    • 什么都没有
  • -serial null -serial mon:stdio(因为 Cortex-A9 有两个 UART)
    • 什么都没有
  • 以上两个加上-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 脚本进行控制。

【问题讨论】:

  • 我的回答还链接到:stackoverflow.com/questions/38914019/…,其中包含两个 UART QEMU 设置。 RPI 运行相同的代码顺便说一句,只是使用不同的 UART 地址,没有“引导加载程序”(除了任何不可避免的东西 RPI 迫使您在有效负载之前运行)。

标签: arm qemu uart bare-metal reliability


【解决方案1】:

您需要在尝试输出任何字符之前初始化 UART。 UART0 仿真运行良好,例如使用稍加修改的 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 示例的命令是:

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

话虽如此,您previous post 的最后一条评论让我觉得您可能只是希望能够看到程序的输出,但不一定要使用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 interface. 多次出现。

希望对你有所帮助。

【讨论】:

  • Semihosting 将是查看程序打印输出的理想方式。由于与my research 相关的原因,我使用了一个不同的构建流程,它使用clang 作为前端。如果我理解您的回答,您的意思是该程序必须以不同方式编译以支持半主机。对吗?
  • @thatjames:这更多的是程序应该链接到不同版本的库,请参阅文件 /opt/arm/9/gcc-arm-9.2-2019.12-aarch64-arm-none-eabi/arm-none-eabi/lib/rdimon.specs 的内容以获取半托管程序应该链接到的库列表。我不熟悉 clang,但是如果您可以链接到 rdimon.specs 中列出的一组 newlib/gcc 库,那么这很可能会起作用。我会对结果感兴趣。
  • @thatjames:如果您认为我正确回答了您的问题,请随时接受答案 - 我花了几个小时来回答您的问题,主要是因为我发现这很有趣/具有挑战性——我猜这个问题不是最初的问题的一部分。
  • @thatjames: rdimon spec 文件的位置显然是错误的,正确的是/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。跨度>
  • @thatjames:根据this paper,看来您可以使用clang 来构建半托管应用程序。
猜你喜欢
  • 2021-07-09
  • 2014-04-29
  • 2012-12-17
  • 2020-03-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-04-13
  • 2012-07-19
相关资源
最近更新 更多