Linux 内核模块作弊 - Qemu Baremetal Xilinx Zynq A9
Linux Kernel Module Cheat - Qemu Baremetal Xilinx Zynq A9
我的目标是模拟 Zynq-7000 上的 ARM A9 处理器,运行宁裸机软件。我已经尝试了 2 种不同的方法来解决这个问题,并且 运行 成为两者的障碍。如有任何关于如何进行的建议,我们将不胜感激。
Whosebug 上的当前答案:
链接到
Linux Kernel Module Cheat(LKMC,使用 v3.0)
使用 ./build --arch arm qemu-baremetal
构建
站点上使用 ARM 虚拟机(-virt
标志)的示例运行良好。试图修改它以适应我的设置是导致问题的原因(详情如下)。
我试图复制示例命令行调用,但使用了 -cpu cortex-a9
选项:
qemu-system-arm: mach-virt: CPU cortex-a9 not supported
然后我将整个调用更改为
qemu-system-arm -M xilinx-zynq-a9 -cpu cortex-a9 -nographic -kernel hello.elf -m 512M -serial mon:stdio -s -S
它因错误
而崩溃
qemu: fatal: Trying to execute code outside RAM or ROM at 0x40000000
这是有道理的,因为该应用程序是使用 LKMC 构建的,而我正试图 运行 在该框架之外。
然后我尝试了 运行我自己的应用程序,它是使用 Xilinx 工具链的修改版本编译的。我确定它不会立即工作,因为我必须更改启动顺序的某些部分。但我正试图弄清楚那些是什么并改变它们。
运行
qemu-system-arm -M xilinx-zynq-a9 -cpu cortex-a9 -nographic -kernel helloworld.elf -m 512M -serial mon:stdio -s -S
允许 GDB 连接成功,但无法正确读取符号 table。使用
arm-none-eabi-objdump -D helloworld.elf
告诉我 main
在 0x001004fc
,但 GDB 认为它在 0x40000324
(使用命令 info address main
)。
我目前的工作
PYNQ-Z1 (webpage, datasheet) 有一个 32 位 ARM Cortex-A9 处理器,所以这就是我使用 qemu-system-arm
而不是 qemu-system-aarch64
的原因。有不对的可以指正。
请注意,我不能简单地切换到不同的体系结构;我正在使用的代码不能容忍除了使板级支持包 (BSP) 与模拟器兼容的小调整之外的更改,而不会损害我研究的有效性。
我已经离开了一段时间的是
./run --arch arm -m 512M --baremetal pynq/helloworld --wait-gdb
./run-gdb --arch arm --baremetal pynq/helloworld --no-continue -- main
我逐步使用 GDB 查找数据中止的位置,并找出 Qemu 不支持的硬件类型。
我 运行ning 的软件是使用修改后的 Xilinx 工具链构建的,因此包括许多 Xilinx 标准库函数。在修改代码以与虚拟机一起工作时,到目前为止我发现了一些变化,例如更改 UART 设备的地址和禁用一些启动任务,例如使 SCU 无效或更改缓存控制器配置,大概是因为Qemu 没有模拟这些东西。
调试启动时,我在启动时遇到的下一个问题 运行 是 XTime 函数 (xtime_l.c)。这些函数是读取全局系统定时器的包装器。 Qemu界面中命令info mtree
的结果似乎表明没有与之交互的全局定时器设备。有没有办法给ARM虚拟机添加一个定时器设备?基地址是什么并不重要,只要能像在Zynq上一样使用,使用寄存器读写即可。
然后我尝试使用特定的机器标志xilinx-zynq-a9
。 LKMC 生成以下命令:
+ /home/$USER/$INSTALL_DIR/out/qemu/default/arm-softmmu/qemu-system-arm \
-machine xilinx-zynq-a9 \
-gdb tcp::45457 \
-kernel /home/$USER/$INSTALL_DIR/out/baremetal/arm/qemu/xilinx-zynq-a9/hello.elf \
-m 512M \
-monitor telnet::45454,server,nowait \
-netdev user,hostfwd=tcp::45455-:45455,hostfwd=tcp::45456-:22,id=net0 \
-no-reboot \
-smp 1 \
-virtfs local,path=/home/$USER/$INSTALL_DIR/data/9p,mount_tag=host_data,security_model=mapped,id=host_data \
-virtfs local,path=/home/$USER/$INSTALL_DIR/out,mount_tag=host_out,security_model=mapped,id=host_out \
-virtfs local,path=/home/$USER/$INSTALL_DIR/rootfs_overlay,mount_tag=host_rootfs_overlay,security_model=mapped,id=host_rootfs_overlay \
-serial mon:stdio \
-trace enable=load_file,file=/home/$USER/$INSTALL_DIR/out/run/qemu/arm/0/trace.bin \
-cpu cortex-a9 \
-device virtio-gpu-pci \
-nographic \
-serial tcp::45458,server,nowait \
-semihosting \
;
这个和通用虚拟机之间的唯一区别是指定机器的行和 cpu,它曾经分别是 -machine virt -machine highmem=off
和 -cpu cortex-a15
(我实际上有修改 LKMC 代码以使其输出正确的 cpu 机器名称)。
然而,这失败并出现错误
qemu-system-arm: -device rtl8139,netdev=net0: No 'PCI' bus found for device 'rtl8139'
这是有道理的,因为并非所有 Zynq 部件都有 PCI 总线。所以我主要想知道为什么 LKMC 会在目标是裸机时生成这样的命令序列。
我认为第一个选项最有可能奏效,因为 -virt
机器似乎比某些特定目标具有更好的支持。有趣的是,Xilinx SDK 附带的 Qemu 版本不支持带 Zynq 的裸机(在 Xilinx Docs 中称为 "standalone")。
总结:
有没有办法给ARM虚拟机添加定时器设备?
有人 运行 Qemu Xilinx ARM A9 上的裸机代码吗?
我已尽量做到具体,但欢迎提出澄清问题。
- 与添加对计时器的支持相比,这可能是一项不平凡的任务
设备到现有的 QEMU 机器。更具体地说,这可能不
需要,因为它们中的相当一部分要么支持 ARM
体系结构计时器或特定计时器硬件。
在 xilinx-zynq-a9 的特定情况下,似乎 Global
Timer Counter
从 Zynq 的第 1448 页描述-7000 技术
支持参考手册。
- 在阅读了您的 post 几遍之后,我得出结论,您使用的工具集(KMC、工具链、QEMU)可能会出现很多问题。因此,我创建了我希望的 Minimal, Reproducible Example 裸机应用程序,它使用我信任的 arm 工具链与 QEMU xilinx-zynq-a9 机器一起工作,以及最新版本的 QEMU 4.2.0,构建于使用我编写的脚本从头开始。
请注意,为了回答您的问题,我改编了一个现有的个人项目,我知道它正在运行。
构建 QEMU:执行 build-qemu.sh
- 此脚本适用于 64 位 Ubuntu 18.04 和 19.10,您必须将 PERL_MODULES_VERSION
设置为 5.28
.
build-qemu.sh
:
#!/bin/bash
set -e
QEMU_VERSION=4.2.0
# xenial
PERL_MODULES_VERSION=5.22
# eoan
PERL_MODULES_VERSION=5.28
# bionic
PERL_MODULES_VERSION=5.26
PREFIX=/opt/qemu-${QEMU_VERSION}
do_install_prerequisites()
{
sudo apt-get install libglib2.0-dev libfdt-dev libpixman-1-dev zlib1g-dev libaio-dev libbluetooth-dev libbrlapi-dev libbz2-dev libcap-dev libcap-ng-dev libcurl4-gnutls-dev libgtk-3-dev libibverbs-dev \
libjpeg8-dev libncurses5-dev libnuma-dev librbd-dev librdmacm-dev libsasl2-dev libsdl2-dev libseccomp-dev libsnappy-dev libssh2-1-dev libvde-dev libvdeplug-dev libvte-2.91-dev libxen-dev liblzo2-dev \
valgrind xfslibs-dev liblzma-dev flex bison texinfo perl perl-modules-${PERL_MODULES_VERSION} python-sphinx gettext
}
do_download_qemu()
{
if [ ! -f qemu-${QEMU_VERSION}.tar.xz ]
then
wget https://download.qemu.org/qemu-${QEMU_VERSION}.tar.xz
fi
}
do_extract_qemu()
{
echo "extracting..."
rm -rf qemu-${QEMU_VERSION}
tar Jxf qemu-${QEMU_VERSION}.tar.xz
}
do_configure_qemu()
{
local TARGET_LIST="arm-softmmu"
pushd qemu-${QEMU_VERSION}
./configure --target-list="${TARGET_LIST}" --prefix=${PREFIX} --extra-cflags="-I$(pwd)/packages/include" --extra-ldflags="-L$(pwd)/packages/lib"
popd
}
do_build_qemu()
{
echo "building..."
pushd qemu-${QEMU_VERSION}
make all
popd
}
do_install_qemu()
{
echo "installing..."
pushd qemu-${QEMU_VERSION}
sudo make install
popd
}
do_build()
{
do_download_qemu
do_extract_qemu
do_configure_qemu
do_build_qemu
do_install_qemu
}
# main
do_install_prerequisites
do_build
脚本完成后,您应该安装了 qemu-system-user 4.2.0:
ls -gG /opt/qemu-4.2.0/bin/
total 22992
-rwxr-xr-x 1 22520 Feb 21 23:57 elf2dmp
-rwxr-xr-x 1 18424 Feb 21 23:57 ivshmem-client
-rwxr-xr-x 1 218264 Feb 21 23:57 ivshmem-server
-rwxr-xr-x 1 30864 Feb 21 23:57 qemu-edid
-rwxr-xr-x 1 374328 Feb 21 23:57 qemu-ga
-rwxr-xr-x 1 1767744 Feb 21 23:57 qemu-img
-rwxr-xr-x 1 1719104 Feb 21 23:57 qemu-io
-rwxr-xr-x 1 505016 Feb 21 23:57 qemu-keymap
-rwxr-xr-x 1 1727744 Feb 21 23:57 qemu-nbd
-rwxr-xr-x 1 599848 Feb 21 23:57 qemu-pr-helper
-rwxr-xr-x 1 16510840 Feb 21 23:57 qemu-system-arm
-rwxr-xr-x 1 26856 Feb 21 23:57 virtfs-proxy-helper
我们现在需要创建以下文件:
gcc_arm32_ram.ld
(改编自标准 GCC CMSIS 5.60 linker script):
/******************************************************************************
* @file gcc_arm32.ld
* @brief GNU Linker Script for Cortex-M based device
* @version V2.0.0
* @date 21. May 2019
******************************************************************************/
/*
* Copyright (c) 2009-2019 Arm Limited. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the License); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an AS IS BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
MEMORY
{
RAM (rwx) : ORIGIN = __RAM_BASE, LENGTH = __RAM_SIZE
}
/* Linker script to place sections and symbol values. Should be used together
* with other linker script that defines memory regions FLASH and RAM.
* It references following symbols, which must be defined in code:
* Reset_Handler : Entry of reset handler
*
* It defines following symbols, which code can use without definition:
* __exidx_start
* __exidx_end
* __copy_table_start__
* __copy_table_end__
* __zero_table_start__
* __zero_table_end__
* __etext
* __data_start__
* __preinit_array_start
* __preinit_array_end
* __init_array_start
* __init_array_end
* __fini_array_start
* __fini_array_end
* __data_end__
* __bss_start__
* __bss_end__
* __end__
* end
* __HeapLimit
* __StackLimit
* __StackTop
* __stack
*/
ENTRY(Reset_Handler)
SECTIONS
{
.text :
{
KEEP(*(.vectors))
*(.text*)
KEEP(*(.init))
KEEP(*(.fini))
/* .ctors */
*crtbegin.o(.ctors)
*crtbegin?.o(.ctors)
*(EXCLUDE_FILE(*crtend?.o *crtend.o) .ctors)
*(SORT(.ctors.*))
*(.ctors)
/* .dtors */
*crtbegin.o(.dtors)
*crtbegin?.o(.dtors)
*(EXCLUDE_FILE(*crtend?.o *crtend.o) .dtors)
*(SORT(.dtors.*))
*(.dtors)
*(.rodata*)
KEEP(*(.eh_frame*))
} > RAM
/*
* SG veneers:
* All SG veneers are placed in the special output section .gnu.sgstubs. Its start address
* must be set, either with the command line option �--section-start� or in a linker script,
* to indicate where to place these veneers in memory.
*/
/*
.gnu.sgstubs :
{
. = ALIGN(32);
} > RAM
*/
.ARM.extab :
{
*(.ARM.extab* .gnu.linkonce.armextab.*)
} > RAM
__exidx_start = .;
.ARM.exidx :
{
*(.ARM.exidx* .gnu.linkonce.armexidx.*)
} > RAM
__exidx_end = .;
.copy.table :
{
. = ALIGN(4);
__copy_table_start__ = .;
LONG (__etext)
LONG (__data_start__)
LONG (__data_end__ - __data_start__)
/* Add each additional data section here */
/*
LONG (__etext2)
LONG (__data2_start__)
LONG (__data2_end__ - __data2_start__)
*/
__copy_table_end__ = .;
} > RAM
.zero.table :
{
. = ALIGN(4);
__zero_table_start__ = .;
/* Add each additional bss section here */
/*
LONG (__bss2_start__)
LONG (__bss2_end__ - __bss2_start__)
*/
__zero_table_end__ = .;
} > RAM
/**
* Location counter can end up 2byte aligned with narrow Thumb code but
* __etext is assumed by startup code to be the LMA of a section in RAM
* which must be 4byte aligned
*/
__etext = ALIGN (4);
.data : AT (__etext)
{
__data_start__ = .;
*(vtable)
*(.data)
*(.data.*)
. = ALIGN(4);
/* preinit data */
PROVIDE_HIDDEN (__preinit_array_start = .);
KEEP(*(.preinit_array))
PROVIDE_HIDDEN (__preinit_array_end = .);
. = ALIGN(4);
/* init data */
PROVIDE_HIDDEN (__init_array_start = .);
KEEP(*(SORT(.init_array.*)))
KEEP(*(.init_array))
PROVIDE_HIDDEN (__init_array_end = .);
. = ALIGN(4);
/* finit data */
PROVIDE_HIDDEN (__fini_array_start = .);
KEEP(*(SORT(.fini_array.*)))
KEEP(*(.fini_array))
PROVIDE_HIDDEN (__fini_array_end = .);
KEEP(*(.jcr*))
. = ALIGN(4);
/* All data end */
__data_end__ = .;
} > RAM
/*
* Secondary data section, optional
*
* Remember to add each additional data section
* to the .copy.table above to asure proper
* initialization during startup.
*/
/*
__etext2 = ALIGN (4);
.data2 : AT (__etext2)
{
. = ALIGN(4);
__data2_start__ = .;
*(.data2)
*(.data2.*)
. = ALIGN(4);
__data2_end__ = .;
} > RAM2
*/
.bss :
{
. = ALIGN(4);
__bss_start__ = .;
*(.bss)
*(.bss.*)
*(COMMON)
. = ALIGN(4);
__bss_end__ = .;
} > RAM AT > RAM
/*
* Secondary bss section, optional
*
* Remember to add each additional bss section
* to the .zero.table above to asure proper
* initialization during startup.
*/
/*
.bss2 :
{
. = ALIGN(4);
__bss2_start__ = .;
*(.bss2)
*(.bss2.*)
. = ALIGN(4);
__bss2_end__ = .;
} > RAM2 AT > RAM2
*/
.heap (COPY) :
{
. = ALIGN(8);
__end__ = .;
PROVIDE(end = .);
. = . + __HEAP_SIZE;
. = ALIGN(8);
__HeapLimit = .;
} > RAM
.stack (ORIGIN(RAM) + LENGTH(RAM) - __STACK_SIZE) (COPY) :
{
. = ALIGN(8);
__StackLimit = .;
. = . + __STACK_SIZE;
. = ALIGN(8);
__StackTop = .;
} > RAM
PROVIDE(__stack = __StackTop);
/* Check if data + heap + stack exceeds RAM limit */
ASSERT(__StackLimit >= __HeapLimit, "region RAM overflowed with stack")
}
Makefile.inc
# Shared Makefile
.PHONY: clean
all: $(MACHINE).elf
$(MACHINE).elf: $(SOURCES) $(MACHINE).c
$(CC) $(CFLAGS) $(LDFLAGS) -o $(MACHINE).elf $(MACHINE).c $(SOURCES)
$(OBJDUMP) -d $(MACHINE).elf > $(MACHINE).lst
qemu: $(MACHINE).elf
$(QEMU_SYSTEM) -m 513M -nographic -machine $(MACHINE) $(QEMU_DEBUG_OPTIONS) -cpu $(CPU) -kernel $(MACHINE).elf
gdb: $(MACHINE).elf
$(GDB) --quiet --command=$(GDB_COMMANDS) $(MACHINE).elf
clean:
rm -f $(MACHINE).elf $(MACHINE).lst
startup-aarch32.s
:
.title startup-aarch32.s
.arch armv7-a
.text
.section .text.startup,"ax"
.globl Reset_Handler
Reset_Handler:
ldr r0, =__StackTop
mov sp, r0
bl start
wait: wfe
b wait
.end
xilinx-zynq-a9.c
:
#include <stdint.h>
/* Reference: https://www.xilinx.com/support/documentation/user_guides/ug585-Zynq-7000-TRM.pdf - page 1449.
1. Read the upper 32-bit timer counter register
2. Read the lower 32-bit timer counter register
3. Read the upper 32-bit timer counter register again.
If the value is different to the32-bit upper value read previously, go back to step 2.
Otherwise the 64-bit timercounter value is correct.
*/
static const uintptr_t Global_Timer_Counter_Register0 = 0xF8F00200;
static const uintptr_t Global_Timer_Counter_Register1 = 0xF8F00204;
void start()
{
uint64_t global_timer_counter = 0;
uint32_t upper = 0;
uint32_t upper2 = 0;
uint32_t lower = 0;
for (;;) {
upper = *(volatile uint32_t*) Global_Timer_Counter_Register1;
lower = *(volatile uint32_t*) Global_Timer_Counter_Register0;
upper2 = *(volatile uint32_t*) Global_Timer_Counter_Register1;
if (upper != upper2) {
lower = *(volatile uint32_t*) Global_Timer_Counter_Register0;
}
global_timer_counter = (uint64_t) upper << 32 | lower;
}
}
xilinx-zynq-a9.gdb
:
target remote localhost:1234
monitor reset halt
load
break Reset_Handler
break start
xilinx-zynq-a9.ld
:
/*
*-------- <<< Use Configuration Wizard in Context Menu >>> -------------------
*/
/*--------------------- Embedded RAM Configuration ----------------------------
<h> RAM Configuration
<o0> RAM Base Address <0x0-0xFFFFFFFF:8>
<o1> RAM Size (in Bytes) <0x0-0xFFFFFFFF:8>
</h>
-----------------------------------------------------------------------------*/
__RAM_BASE = 0x00100000;
__RAM_SIZE = 0x20000000;
/*--------------------- Stack / Heap Configuration ----------------------------
<h> Stack / Heap Configuration
<o0> Stack Size (in Bytes) <0x0-0xFFFFFFFF:8>
<o1> Heap Size (in Bytes) <0x0-0xFFFFFFFF:8>
</h>
-----------------------------------------------------------------------------*/
__STACK_SIZE = 0x00020000;
__HEAP_SIZE = 0x00080000;
/*
*-------------------- <<< end of configuration section >>> -------------------
*/
INCLUDE gcc_arm32_ram.ld
我指定了一个 512MiB 的 DDR RAM 区域,从 0x20000000 开始 - 有关 Zynq-7000 memory map 的更多信息,请参见此处。
xilinx-zynq-a9.mak
:
# Toolchain
CROSS_COMPILE=/opt/arm/9/gcc-arm-9.2-2019.12-x86_64-arm-none-eabi/bin/arm-none-eabi-
CC=$(CROSS_COMPILE)gcc
OBJDUMP=$(CROSS_COMPILE)objdump
OBJCOPY=$(CROSS_COMPILE)objcopy
# Target
CPU=cortex-a9
MACHINE=xilinx-zynq-a9
CFLAGS=-O0 -ggdb -mtune=$(CPU) -nostdlib -nostartfiles -ffreestanding
LDFLAGS=-L. -Wl,-T,$(MACHINE).ld
SOURCES=startup-aarch32.s
# qemu
QEMU_DEBUG_OPTIONS=-S -gdb tcp::1234,ipv4
QEMU_SYSTEM=/opt/qemu-4.2.0/bin/qemu-system-arm
# GDB
GDB=$(CROSS_COMPILE)gdb
GDB_COMMANDS=${MACHINE}.gdb
include Makefile.inc
您现在需要安装 latest GCC toolchain provided by arm:
wget "https://developer.arm.com/-/media/Files/downloads/gnu-a/9.2-2019.12/binrel/gcc-arm-9.2-2019.12-x86_64-arm-none-eabi.tar.xz?revision=64186c5d-b471-4c97-a8f5-b1b300d6594a&la=en&hash=5E9204DA5AF0B055B5B0F50C53E185FAA10FF625" -o gcc-arm-9.2-2019.12-x86_64-arm-none-eabi.tar.xz
mkdir -p /opt/arm/9
tar Jxf gcc-arm-9.2-2019.12-x86_64-arm-none-eabi.tar.xz -C /opt/arm/9
您现在可以compile/execute/debug示例:
make -f xilinx-zynq-a9.mak clean all
您应该得到以下输出:
rm -f xilinx-zynq-a9.elf xilinx-zynq-a9.lst
/opt/arm/9/gcc-arm-9.2-2019.12-x86_64-arm-none-eabi/bin/arm-none-eabi-gcc -O0 -ggdb -mtune=cortex-a9 -nostdlib -nostartfiles -ffreestanding -L. -Wl,-T,xilinx-zynq-a9.ld -o xilinx-zynq-a9.elf xilinx-zynq-a9.c startup-aarch32.s
/opt/arm/9/gcc-arm-9.2-2019.12-x86_64-arm-none-eabi/bin/arm-none-eabi-objdump -d xilinx-zynq-a9.elf > xilinx-zynq-a9.lst
您可以启动 QEMU:
make -f xilinx-zynq-a9.mak qemu
应该显示执行的QEMU命令:
/opt/qemu-4.2.0/bin/qemu-system-arm -m 513M -nographic -machine xilinx-zynq-a9 -S -gdb tcp::1234,ipv4 -cpu cortex-a9 -kernel xilinx-zynq-a9.elf
在另一个shell中,启动GDB:
make -f xilinx-zynq-a9.mak gdb
您应该看到以下输出:
/opt/arm/9/gcc-arm-9.2-2019.12-x86_64-arm-none-eabi/bin/arm-none-eabi-gdb --quiet --command=xilinx-zynq-a9.gdb xilinx-zynq-a9.elf
Reading symbols from xilinx-zynq-a9.elf...
Reset_Handler () at startup-aarch32.s:7
7 ldr r0, =__StackTop
unknown command: 'reset'
Loading section .text, size 0xd8 lma 0x100000
Loading section .copy.table, size 0xc lma 0x1000d8
Start address 0x1000b8, load size 228
Transfer rate: 222 KB/sec, 114 bytes/write.
Breakpoint 1 at 0x1000b8: file startup-aarch32.s, line 7.
Breakpoint 2 at 0x10000c: file xilinx-zynq-a9.c, line 17.
(gdb)
执行continue
:
(gdb) continue
Continuing.
Breakpoint 2, start () at xilinx-zynq-a9.c:17
17 uint64_t global_timer_counter = 0;
现在,执行几个 step
命令,并在每行显示 global_timer_counter
变量:
31 global_timer_counter = (uint64_t) upper << 32 | lower;
被执行:
(gdb) p/x global_timer_counter
= 0xa6f2a0a
(gdb) p/x global_timer_counter
= 0xa84315b
(gdb) p/x global_timer_counter
= 0xabe77cf
64 位变量不断递增,这与 QEMU
的 Zynq 全局定时器计数器的工作仿真一致,我们现在有一个工作的裸机 Zynq-7000 示例,可以使用GDB.
希望我回答了您提出的两个问题。
我的目标是模拟 Zynq-7000 上的 ARM A9 处理器,运行宁裸机软件。我已经尝试了 2 种不同的方法来解决这个问题,并且 运行 成为两者的障碍。如有任何关于如何进行的建议,我们将不胜感激。
Whosebug 上的当前答案:
链接到
Linux Kernel Module Cheat(LKMC,使用 v3.0)
使用 ./build --arch arm qemu-baremetal
站点上使用 ARM 虚拟机(-virt
标志)的示例运行良好。试图修改它以适应我的设置是导致问题的原因(详情如下)。
我试图复制示例命令行调用,但使用了 -cpu cortex-a9
选项:
qemu-system-arm: mach-virt: CPU cortex-a9 not supported
然后我将整个调用更改为
qemu-system-arm -M xilinx-zynq-a9 -cpu cortex-a9 -nographic -kernel hello.elf -m 512M -serial mon:stdio -s -S
它因错误
而崩溃
qemu: fatal: Trying to execute code outside RAM or ROM at 0x40000000
这是有道理的,因为该应用程序是使用 LKMC 构建的,而我正试图 运行 在该框架之外。
然后我尝试了 运行我自己的应用程序,它是使用 Xilinx 工具链的修改版本编译的。我确定它不会立即工作,因为我必须更改启动顺序的某些部分。但我正试图弄清楚那些是什么并改变它们。
运行
qemu-system-arm -M xilinx-zynq-a9 -cpu cortex-a9 -nographic -kernel helloworld.elf -m 512M -serial mon:stdio -s -S
允许 GDB 连接成功,但无法正确读取符号 table。使用
arm-none-eabi-objdump -D helloworld.elf
告诉我 main
在 0x001004fc
,但 GDB 认为它在 0x40000324
(使用命令 info address main
)。
我目前的工作
PYNQ-Z1 (webpage, datasheet) 有一个 32 位 ARM Cortex-A9 处理器,所以这就是我使用 qemu-system-arm
而不是 qemu-system-aarch64
的原因。有不对的可以指正。
请注意,我不能简单地切换到不同的体系结构;我正在使用的代码不能容忍除了使板级支持包 (BSP) 与模拟器兼容的小调整之外的更改,而不会损害我研究的有效性。
我已经离开了一段时间的是
./run --arch arm -m 512M --baremetal pynq/helloworld --wait-gdb
./run-gdb --arch arm --baremetal pynq/helloworld --no-continue -- main
我逐步使用 GDB 查找数据中止的位置,并找出 Qemu 不支持的硬件类型。
我 运行ning 的软件是使用修改后的 Xilinx 工具链构建的,因此包括许多 Xilinx 标准库函数。在修改代码以与虚拟机一起工作时,到目前为止我发现了一些变化,例如更改 UART 设备的地址和禁用一些启动任务,例如使 SCU 无效或更改缓存控制器配置,大概是因为Qemu 没有模拟这些东西。
调试启动时,我在启动时遇到的下一个问题 运行 是 XTime 函数 (xtime_l.c)。这些函数是读取全局系统定时器的包装器。 Qemu界面中命令info mtree
的结果似乎表明没有与之交互的全局定时器设备。有没有办法给ARM虚拟机添加一个定时器设备?基地址是什么并不重要,只要能像在Zynq上一样使用,使用寄存器读写即可。
然后我尝试使用特定的机器标志xilinx-zynq-a9
。 LKMC 生成以下命令:
+ /home/$USER/$INSTALL_DIR/out/qemu/default/arm-softmmu/qemu-system-arm \
-machine xilinx-zynq-a9 \
-gdb tcp::45457 \
-kernel /home/$USER/$INSTALL_DIR/out/baremetal/arm/qemu/xilinx-zynq-a9/hello.elf \
-m 512M \
-monitor telnet::45454,server,nowait \
-netdev user,hostfwd=tcp::45455-:45455,hostfwd=tcp::45456-:22,id=net0 \
-no-reboot \
-smp 1 \
-virtfs local,path=/home/$USER/$INSTALL_DIR/data/9p,mount_tag=host_data,security_model=mapped,id=host_data \
-virtfs local,path=/home/$USER/$INSTALL_DIR/out,mount_tag=host_out,security_model=mapped,id=host_out \
-virtfs local,path=/home/$USER/$INSTALL_DIR/rootfs_overlay,mount_tag=host_rootfs_overlay,security_model=mapped,id=host_rootfs_overlay \
-serial mon:stdio \
-trace enable=load_file,file=/home/$USER/$INSTALL_DIR/out/run/qemu/arm/0/trace.bin \
-cpu cortex-a9 \
-device virtio-gpu-pci \
-nographic \
-serial tcp::45458,server,nowait \
-semihosting \
;
这个和通用虚拟机之间的唯一区别是指定机器的行和 cpu,它曾经分别是 -machine virt -machine highmem=off
和 -cpu cortex-a15
(我实际上有修改 LKMC 代码以使其输出正确的 cpu 机器名称)。
然而,这失败并出现错误
qemu-system-arm: -device rtl8139,netdev=net0: No 'PCI' bus found for device 'rtl8139'
这是有道理的,因为并非所有 Zynq 部件都有 PCI 总线。所以我主要想知道为什么 LKMC 会在目标是裸机时生成这样的命令序列。
我认为第一个选项最有可能奏效,因为 -virt
机器似乎比某些特定目标具有更好的支持。有趣的是,Xilinx SDK 附带的 Qemu 版本不支持带 Zynq 的裸机(在 Xilinx Docs 中称为 "standalone")。
总结:
有没有办法给ARM虚拟机添加定时器设备?
有人 运行 Qemu Xilinx ARM A9 上的裸机代码吗?
我已尽量做到具体,但欢迎提出澄清问题。
- 与添加对计时器的支持相比,这可能是一项不平凡的任务
设备到现有的 QEMU 机器。更具体地说,这可能不
需要,因为它们中的相当一部分要么支持 ARM
体系结构计时器或特定计时器硬件。
在 xilinx-zynq-a9 的特定情况下,似乎Global Timer Counter
从 Zynq 的第 1448 页描述-7000 技术 支持参考手册。 - 在阅读了您的 post 几遍之后,我得出结论,您使用的工具集(KMC、工具链、QEMU)可能会出现很多问题。因此,我创建了我希望的 Minimal, Reproducible Example 裸机应用程序,它使用我信任的 arm 工具链与 QEMU xilinx-zynq-a9 机器一起工作,以及最新版本的 QEMU 4.2.0,构建于使用我编写的脚本从头开始。
请注意,为了回答您的问题,我改编了一个现有的个人项目,我知道它正在运行。
构建 QEMU:执行 build-qemu.sh
- 此脚本适用于 64 位 Ubuntu 18.04 和 19.10,您必须将 PERL_MODULES_VERSION
设置为 5.28
.
build-qemu.sh
:
#!/bin/bash
set -e
QEMU_VERSION=4.2.0
# xenial
PERL_MODULES_VERSION=5.22
# eoan
PERL_MODULES_VERSION=5.28
# bionic
PERL_MODULES_VERSION=5.26
PREFIX=/opt/qemu-${QEMU_VERSION}
do_install_prerequisites()
{
sudo apt-get install libglib2.0-dev libfdt-dev libpixman-1-dev zlib1g-dev libaio-dev libbluetooth-dev libbrlapi-dev libbz2-dev libcap-dev libcap-ng-dev libcurl4-gnutls-dev libgtk-3-dev libibverbs-dev \
libjpeg8-dev libncurses5-dev libnuma-dev librbd-dev librdmacm-dev libsasl2-dev libsdl2-dev libseccomp-dev libsnappy-dev libssh2-1-dev libvde-dev libvdeplug-dev libvte-2.91-dev libxen-dev liblzo2-dev \
valgrind xfslibs-dev liblzma-dev flex bison texinfo perl perl-modules-${PERL_MODULES_VERSION} python-sphinx gettext
}
do_download_qemu()
{
if [ ! -f qemu-${QEMU_VERSION}.tar.xz ]
then
wget https://download.qemu.org/qemu-${QEMU_VERSION}.tar.xz
fi
}
do_extract_qemu()
{
echo "extracting..."
rm -rf qemu-${QEMU_VERSION}
tar Jxf qemu-${QEMU_VERSION}.tar.xz
}
do_configure_qemu()
{
local TARGET_LIST="arm-softmmu"
pushd qemu-${QEMU_VERSION}
./configure --target-list="${TARGET_LIST}" --prefix=${PREFIX} --extra-cflags="-I$(pwd)/packages/include" --extra-ldflags="-L$(pwd)/packages/lib"
popd
}
do_build_qemu()
{
echo "building..."
pushd qemu-${QEMU_VERSION}
make all
popd
}
do_install_qemu()
{
echo "installing..."
pushd qemu-${QEMU_VERSION}
sudo make install
popd
}
do_build()
{
do_download_qemu
do_extract_qemu
do_configure_qemu
do_build_qemu
do_install_qemu
}
# main
do_install_prerequisites
do_build
脚本完成后,您应该安装了 qemu-system-user 4.2.0:
ls -gG /opt/qemu-4.2.0/bin/
total 22992
-rwxr-xr-x 1 22520 Feb 21 23:57 elf2dmp
-rwxr-xr-x 1 18424 Feb 21 23:57 ivshmem-client
-rwxr-xr-x 1 218264 Feb 21 23:57 ivshmem-server
-rwxr-xr-x 1 30864 Feb 21 23:57 qemu-edid
-rwxr-xr-x 1 374328 Feb 21 23:57 qemu-ga
-rwxr-xr-x 1 1767744 Feb 21 23:57 qemu-img
-rwxr-xr-x 1 1719104 Feb 21 23:57 qemu-io
-rwxr-xr-x 1 505016 Feb 21 23:57 qemu-keymap
-rwxr-xr-x 1 1727744 Feb 21 23:57 qemu-nbd
-rwxr-xr-x 1 599848 Feb 21 23:57 qemu-pr-helper
-rwxr-xr-x 1 16510840 Feb 21 23:57 qemu-system-arm
-rwxr-xr-x 1 26856 Feb 21 23:57 virtfs-proxy-helper
我们现在需要创建以下文件:
gcc_arm32_ram.ld
(改编自标准 GCC CMSIS 5.60 linker script):
/******************************************************************************
* @file gcc_arm32.ld
* @brief GNU Linker Script for Cortex-M based device
* @version V2.0.0
* @date 21. May 2019
******************************************************************************/
/*
* Copyright (c) 2009-2019 Arm Limited. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the License); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an AS IS BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
MEMORY
{
RAM (rwx) : ORIGIN = __RAM_BASE, LENGTH = __RAM_SIZE
}
/* Linker script to place sections and symbol values. Should be used together
* with other linker script that defines memory regions FLASH and RAM.
* It references following symbols, which must be defined in code:
* Reset_Handler : Entry of reset handler
*
* It defines following symbols, which code can use without definition:
* __exidx_start
* __exidx_end
* __copy_table_start__
* __copy_table_end__
* __zero_table_start__
* __zero_table_end__
* __etext
* __data_start__
* __preinit_array_start
* __preinit_array_end
* __init_array_start
* __init_array_end
* __fini_array_start
* __fini_array_end
* __data_end__
* __bss_start__
* __bss_end__
* __end__
* end
* __HeapLimit
* __StackLimit
* __StackTop
* __stack
*/
ENTRY(Reset_Handler)
SECTIONS
{
.text :
{
KEEP(*(.vectors))
*(.text*)
KEEP(*(.init))
KEEP(*(.fini))
/* .ctors */
*crtbegin.o(.ctors)
*crtbegin?.o(.ctors)
*(EXCLUDE_FILE(*crtend?.o *crtend.o) .ctors)
*(SORT(.ctors.*))
*(.ctors)
/* .dtors */
*crtbegin.o(.dtors)
*crtbegin?.o(.dtors)
*(EXCLUDE_FILE(*crtend?.o *crtend.o) .dtors)
*(SORT(.dtors.*))
*(.dtors)
*(.rodata*)
KEEP(*(.eh_frame*))
} > RAM
/*
* SG veneers:
* All SG veneers are placed in the special output section .gnu.sgstubs. Its start address
* must be set, either with the command line option �--section-start� or in a linker script,
* to indicate where to place these veneers in memory.
*/
/*
.gnu.sgstubs :
{
. = ALIGN(32);
} > RAM
*/
.ARM.extab :
{
*(.ARM.extab* .gnu.linkonce.armextab.*)
} > RAM
__exidx_start = .;
.ARM.exidx :
{
*(.ARM.exidx* .gnu.linkonce.armexidx.*)
} > RAM
__exidx_end = .;
.copy.table :
{
. = ALIGN(4);
__copy_table_start__ = .;
LONG (__etext)
LONG (__data_start__)
LONG (__data_end__ - __data_start__)
/* Add each additional data section here */
/*
LONG (__etext2)
LONG (__data2_start__)
LONG (__data2_end__ - __data2_start__)
*/
__copy_table_end__ = .;
} > RAM
.zero.table :
{
. = ALIGN(4);
__zero_table_start__ = .;
/* Add each additional bss section here */
/*
LONG (__bss2_start__)
LONG (__bss2_end__ - __bss2_start__)
*/
__zero_table_end__ = .;
} > RAM
/**
* Location counter can end up 2byte aligned with narrow Thumb code but
* __etext is assumed by startup code to be the LMA of a section in RAM
* which must be 4byte aligned
*/
__etext = ALIGN (4);
.data : AT (__etext)
{
__data_start__ = .;
*(vtable)
*(.data)
*(.data.*)
. = ALIGN(4);
/* preinit data */
PROVIDE_HIDDEN (__preinit_array_start = .);
KEEP(*(.preinit_array))
PROVIDE_HIDDEN (__preinit_array_end = .);
. = ALIGN(4);
/* init data */
PROVIDE_HIDDEN (__init_array_start = .);
KEEP(*(SORT(.init_array.*)))
KEEP(*(.init_array))
PROVIDE_HIDDEN (__init_array_end = .);
. = ALIGN(4);
/* finit data */
PROVIDE_HIDDEN (__fini_array_start = .);
KEEP(*(SORT(.fini_array.*)))
KEEP(*(.fini_array))
PROVIDE_HIDDEN (__fini_array_end = .);
KEEP(*(.jcr*))
. = ALIGN(4);
/* All data end */
__data_end__ = .;
} > RAM
/*
* Secondary data section, optional
*
* Remember to add each additional data section
* to the .copy.table above to asure proper
* initialization during startup.
*/
/*
__etext2 = ALIGN (4);
.data2 : AT (__etext2)
{
. = ALIGN(4);
__data2_start__ = .;
*(.data2)
*(.data2.*)
. = ALIGN(4);
__data2_end__ = .;
} > RAM2
*/
.bss :
{
. = ALIGN(4);
__bss_start__ = .;
*(.bss)
*(.bss.*)
*(COMMON)
. = ALIGN(4);
__bss_end__ = .;
} > RAM AT > RAM
/*
* Secondary bss section, optional
*
* Remember to add each additional bss section
* to the .zero.table above to asure proper
* initialization during startup.
*/
/*
.bss2 :
{
. = ALIGN(4);
__bss2_start__ = .;
*(.bss2)
*(.bss2.*)
. = ALIGN(4);
__bss2_end__ = .;
} > RAM2 AT > RAM2
*/
.heap (COPY) :
{
. = ALIGN(8);
__end__ = .;
PROVIDE(end = .);
. = . + __HEAP_SIZE;
. = ALIGN(8);
__HeapLimit = .;
} > RAM
.stack (ORIGIN(RAM) + LENGTH(RAM) - __STACK_SIZE) (COPY) :
{
. = ALIGN(8);
__StackLimit = .;
. = . + __STACK_SIZE;
. = ALIGN(8);
__StackTop = .;
} > RAM
PROVIDE(__stack = __StackTop);
/* Check if data + heap + stack exceeds RAM limit */
ASSERT(__StackLimit >= __HeapLimit, "region RAM overflowed with stack")
}
Makefile.inc
# Shared Makefile
.PHONY: clean
all: $(MACHINE).elf
$(MACHINE).elf: $(SOURCES) $(MACHINE).c
$(CC) $(CFLAGS) $(LDFLAGS) -o $(MACHINE).elf $(MACHINE).c $(SOURCES)
$(OBJDUMP) -d $(MACHINE).elf > $(MACHINE).lst
qemu: $(MACHINE).elf
$(QEMU_SYSTEM) -m 513M -nographic -machine $(MACHINE) $(QEMU_DEBUG_OPTIONS) -cpu $(CPU) -kernel $(MACHINE).elf
gdb: $(MACHINE).elf
$(GDB) --quiet --command=$(GDB_COMMANDS) $(MACHINE).elf
clean:
rm -f $(MACHINE).elf $(MACHINE).lst
startup-aarch32.s
:
.title startup-aarch32.s
.arch armv7-a
.text
.section .text.startup,"ax"
.globl Reset_Handler
Reset_Handler:
ldr r0, =__StackTop
mov sp, r0
bl start
wait: wfe
b wait
.end
xilinx-zynq-a9.c
:
#include <stdint.h>
/* Reference: https://www.xilinx.com/support/documentation/user_guides/ug585-Zynq-7000-TRM.pdf - page 1449.
1. Read the upper 32-bit timer counter register
2. Read the lower 32-bit timer counter register
3. Read the upper 32-bit timer counter register again.
If the value is different to the32-bit upper value read previously, go back to step 2.
Otherwise the 64-bit timercounter value is correct.
*/
static const uintptr_t Global_Timer_Counter_Register0 = 0xF8F00200;
static const uintptr_t Global_Timer_Counter_Register1 = 0xF8F00204;
void start()
{
uint64_t global_timer_counter = 0;
uint32_t upper = 0;
uint32_t upper2 = 0;
uint32_t lower = 0;
for (;;) {
upper = *(volatile uint32_t*) Global_Timer_Counter_Register1;
lower = *(volatile uint32_t*) Global_Timer_Counter_Register0;
upper2 = *(volatile uint32_t*) Global_Timer_Counter_Register1;
if (upper != upper2) {
lower = *(volatile uint32_t*) Global_Timer_Counter_Register0;
}
global_timer_counter = (uint64_t) upper << 32 | lower;
}
}
xilinx-zynq-a9.gdb
:
target remote localhost:1234
monitor reset halt
load
break Reset_Handler
break start
xilinx-zynq-a9.ld
:
/*
*-------- <<< Use Configuration Wizard in Context Menu >>> -------------------
*/
/*--------------------- Embedded RAM Configuration ----------------------------
<h> RAM Configuration
<o0> RAM Base Address <0x0-0xFFFFFFFF:8>
<o1> RAM Size (in Bytes) <0x0-0xFFFFFFFF:8>
</h>
-----------------------------------------------------------------------------*/
__RAM_BASE = 0x00100000;
__RAM_SIZE = 0x20000000;
/*--------------------- Stack / Heap Configuration ----------------------------
<h> Stack / Heap Configuration
<o0> Stack Size (in Bytes) <0x0-0xFFFFFFFF:8>
<o1> Heap Size (in Bytes) <0x0-0xFFFFFFFF:8>
</h>
-----------------------------------------------------------------------------*/
__STACK_SIZE = 0x00020000;
__HEAP_SIZE = 0x00080000;
/*
*-------------------- <<< end of configuration section >>> -------------------
*/
INCLUDE gcc_arm32_ram.ld
我指定了一个 512MiB 的 DDR RAM 区域,从 0x20000000 开始 - 有关 Zynq-7000 memory map 的更多信息,请参见此处。
xilinx-zynq-a9.mak
:
# Toolchain
CROSS_COMPILE=/opt/arm/9/gcc-arm-9.2-2019.12-x86_64-arm-none-eabi/bin/arm-none-eabi-
CC=$(CROSS_COMPILE)gcc
OBJDUMP=$(CROSS_COMPILE)objdump
OBJCOPY=$(CROSS_COMPILE)objcopy
# Target
CPU=cortex-a9
MACHINE=xilinx-zynq-a9
CFLAGS=-O0 -ggdb -mtune=$(CPU) -nostdlib -nostartfiles -ffreestanding
LDFLAGS=-L. -Wl,-T,$(MACHINE).ld
SOURCES=startup-aarch32.s
# qemu
QEMU_DEBUG_OPTIONS=-S -gdb tcp::1234,ipv4
QEMU_SYSTEM=/opt/qemu-4.2.0/bin/qemu-system-arm
# GDB
GDB=$(CROSS_COMPILE)gdb
GDB_COMMANDS=${MACHINE}.gdb
include Makefile.inc
您现在需要安装 latest GCC toolchain provided by arm:
wget "https://developer.arm.com/-/media/Files/downloads/gnu-a/9.2-2019.12/binrel/gcc-arm-9.2-2019.12-x86_64-arm-none-eabi.tar.xz?revision=64186c5d-b471-4c97-a8f5-b1b300d6594a&la=en&hash=5E9204DA5AF0B055B5B0F50C53E185FAA10FF625" -o gcc-arm-9.2-2019.12-x86_64-arm-none-eabi.tar.xz
mkdir -p /opt/arm/9
tar Jxf gcc-arm-9.2-2019.12-x86_64-arm-none-eabi.tar.xz -C /opt/arm/9
您现在可以compile/execute/debug示例:
make -f xilinx-zynq-a9.mak clean all
您应该得到以下输出:
rm -f xilinx-zynq-a9.elf xilinx-zynq-a9.lst
/opt/arm/9/gcc-arm-9.2-2019.12-x86_64-arm-none-eabi/bin/arm-none-eabi-gcc -O0 -ggdb -mtune=cortex-a9 -nostdlib -nostartfiles -ffreestanding -L. -Wl,-T,xilinx-zynq-a9.ld -o xilinx-zynq-a9.elf xilinx-zynq-a9.c startup-aarch32.s
/opt/arm/9/gcc-arm-9.2-2019.12-x86_64-arm-none-eabi/bin/arm-none-eabi-objdump -d xilinx-zynq-a9.elf > xilinx-zynq-a9.lst
您可以启动 QEMU:
make -f xilinx-zynq-a9.mak qemu
应该显示执行的QEMU命令:
/opt/qemu-4.2.0/bin/qemu-system-arm -m 513M -nographic -machine xilinx-zynq-a9 -S -gdb tcp::1234,ipv4 -cpu cortex-a9 -kernel xilinx-zynq-a9.elf
在另一个shell中,启动GDB:
make -f xilinx-zynq-a9.mak gdb
您应该看到以下输出:
/opt/arm/9/gcc-arm-9.2-2019.12-x86_64-arm-none-eabi/bin/arm-none-eabi-gdb --quiet --command=xilinx-zynq-a9.gdb xilinx-zynq-a9.elf
Reading symbols from xilinx-zynq-a9.elf...
Reset_Handler () at startup-aarch32.s:7
7 ldr r0, =__StackTop
unknown command: 'reset'
Loading section .text, size 0xd8 lma 0x100000
Loading section .copy.table, size 0xc lma 0x1000d8
Start address 0x1000b8, load size 228
Transfer rate: 222 KB/sec, 114 bytes/write.
Breakpoint 1 at 0x1000b8: file startup-aarch32.s, line 7.
Breakpoint 2 at 0x10000c: file xilinx-zynq-a9.c, line 17.
(gdb)
执行continue
:
(gdb) continue
Continuing.
Breakpoint 2, start () at xilinx-zynq-a9.c:17
17 uint64_t global_timer_counter = 0;
现在,执行几个 step
命令,并在每行显示 global_timer_counter
变量:
31 global_timer_counter = (uint64_t) upper << 32 | lower;
被执行:
(gdb) p/x global_timer_counter
= 0xa6f2a0a
(gdb) p/x global_timer_counter
= 0xa84315b
(gdb) p/x global_timer_counter
= 0xabe77cf
64 位变量不断递增,这与 QEMU
的 Zynq 全局定时器计数器的工作仿真一致,我们现在有一个工作的裸机 Zynq-7000 示例,可以使用GDB.
希望我回答了您提出的两个问题。