【发布时间】:2020-08-27 06:01:28
【问题描述】:
在主要的操作系统升级后,此 C 代码行为发生了变化:
...
if ((fd = open(argv[1], O_RDWR | O_SYNC)) == -1)
FATAL;
printf("character device %s opened.\n", argv[1]);
fflush(stdout);
/* map one page */
map_base = mmap(0xe0000000, MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (map_base == (void *)-1)
FATAL;
printf("Memory mapped at address %p.\n", map_base);
...
对于从旧操作系统继承的二进制文件,“旧 mmap”返回虚拟地址 0x7fb20d725000。如果我在新操作系统上重建相同的 C 文件,它会返回 0xe0000000,这似乎是一个物理的,随后的代码 - 使用这个返回的地址 - 现在因分段错误而失败。
如何在不降级操作系统或使用旧二进制文件的情况下强制mmap 像以前一样工作? gcc 或 mmap 本身的任何现代标志?
使用sudo ./test /dev/zero 0x01000000 运行下面的代码示例:(/dev/zero 代替真实设备会得到相同的结果)
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <byteswap.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <fcntl.h>
#include <ctype.h>
#include <termios.h>
#include <sys/types.h>
#include <sys/mman.h>
/* ltoh: little to host */
/* htol: little to host */
#if __BYTE_ORDER == __LITTLE_ENDIAN
#define ltohl(x) (x)
#define ltohs(x) (x)
#define htoll(x) (x)
#define htols(x) (x)
#elif __BYTE_ORDER == __BIG_ENDIAN
#define ltohl(x) __bswap_32(x)
#define ltohs(x) __bswap_16(x)
#define htoll(x) __bswap_32(x)
#define htols(x) __bswap_16(x)
#endif
#define FATAL do { fprintf(stderr, "Error at line %d, file %s (%d) [%s]\n", __LINE__, __FILE__, errno, strerror(errno)); exit(1); } while(0)
#define MAP_SIZE (16*1024*1024UL)
#define MAP_MASK (MAP_SIZE - 1)
int main(int argc, char **argv)
{
int fd;
void *map_base, *virt_addr;
uint32_t read_result, writeval;
off_t target;
char *device;
if (argc != 3) {
fprintf(stderr,
"\nUsage:\t%s <device> <address> [[type] data]\n"
"\tdevice : character device to access\n"
"\taddress : memory address to access\n\n",
argv[0]);
exit(1);
}
device = strdup(argv[1]);
target = strtoul(argv[2], 0, 0);
fprintf("argc = %d, device: %s, address: 0x%08x\n", argc, device, (unsigned int)target);
if ((fd = open(argv[1], O_RDWR | O_SYNC)) == -1)
FATAL;
fprintf(stdout, "character device %s opened.\n", argv[1]);
fflush(stdout);
/* map one page */
map_base = mmap(0xe0000000, MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (map_base == (void *)-1)
FATAL;
fprintf(stdout, "Memory mapped at address %p.\n", map_base);
fflush(stdout);
/* calculate the virtual address to be accessed */
virt_addr = map_base + target;
/* read only */
read_result = *((uint32_t *) virt_addr);
/* swap 32-bit endianess if host is not little-endian */
read_result = ltohl(read_result);
printf("Read 32-bit value at address 0x%08x (%p): 0x%08x\n",
(unsigned int)target, virt_addr, (unsigned int)read_result);
if (munmap(map_base, MAP_SIZE) == -1)
FATAL;
close(fd);
return 0;
}
【问题讨论】:
-
您是否更改了代码中的任何内容?比如头文件或者你编译的方式?请创建一个minimal reproducible example。我也很好奇
FATAL是如何定义的 -
您必须比这更具体。映射后如何使用页面? 什么 因分段错误而失败?您为什么首先将其映射到该地址?你在什么架构上?我想x86 64位,对吗?此外,这绝对是不是一个物理地址。请提供可以编译以重现问题的最小示例代码。
-
请减少代码。这绝对不是最小的
-
@qmastery 您的代码看起来和工作正常,除了将文件作为第一个参数的
fprintf和您应该将地址转换为void *的mmap。我没有看到任何明显的错误。您确定运行它会导致分段错误吗?如果是这种情况,添加内核版本 (uname -r) 可能也会有所帮助。 -
@qmastery 当然,如果您映射
16 * 1024 * 1024(即0x01000000)然后您尝试读取超出此范围的内容(即任何高于或等于0x01000000的偏移量),就会发生这种情况,这只是映射区域的超大)。你能指望什么?当然会导致分段错误。没什么好讨论的……