c - 虚拟地址到物理地址的段错误

标签 c linux memory-management

当我运行这段代码的二进制文件时,它会抛出一个段错误核心转储错误。 dmesg 是:

segfault at 0 ip b7651747 sp bfb312d0 error 4 in libc-2.21.so[b75e9000+1b4000]

该代码用于将虚拟地址转换为物理地址。

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#include <errno.h>
#include <fcntl.h>
#include <stdint.h>

// ORIG_BUFFER will be placed in memory and will then be changed to NEW_BUFFER
// They must be the same length
#define ORIG_BUFFER "Hello, World!"
#define NEW_BUFFER "Hello, Linux!"

// The page frame shifted left by PAGE_SHIFT will give us the physcial address of the frame
// Note that this number is architecture dependent. For me on x86_64 with 4096 page sizes,
// it is defined as 12. If you're running something different, check the kernel source
// for what it is defined as.
#define PAGE_SHIFT 12
#define PAGEMAP_LENGTH 8

void* create_buffer(void);
unsigned long get_page_frame_number_of_address(void *addr);
int open_memory(void);
void seek_memory(int fd, unsigned long offset);

int main(void) {
  // Create a buffer with some data in it
  void *buffer = create_buffer();

  // Get the page frame the buffer is on
  unsigned int page_frame_number = get_page_frame_number_of_address(buffer);
  printf("Page frame: 0x%x\n", page_frame_number);

  // Find the difference from the buffer to the page boundary
  unsigned int distance_from_page_boundary = (unsigned long)buffer %
  getpagesize();

  // Determine how far to seek into memory to find the buffer
  uint64_t offset = (page_frame_number << PAGE_SHIFT) + distance_from_page_boundary;

  // Open /dev/mem, seek the calculated offset, and
  // map it into memory so we can manipulate it
  // CONFIG_STRICT_DEVMEM must be disabled for this
  int mem_fd = open_memory();
  seek_memory(mem_fd, offset);

  printf("Buffer: %s\n", buffer);
  puts("Changing buffer through /dev/mem...");

  // Change the contents of the buffer by writing into /dev/mem
  // Note that since the strings are the same length, there's no purpose in
  // copying the NUL terminator again
  if(write(mem_fd, NEW_BUFFER, strlen(NEW_BUFFER)) == -1) {
    fprintf(stderr, "Write failed: %s\n", strerror(errno));
  }

  printf("Buffer: %s\n", buffer);

  // Clean up
  free(buffer);
  close(mem_fd);

  return 0;
}

void* create_buffer(void) {
  size_t buf_size = strlen(ORIG_BUFFER) + 1;

  // Allocate some memory to manipulate
  void *buffer = malloc(buf_size);
  if(buffer == NULL) {
    fprintf(stderr, "Failed to allocate memory for buffer\n");
    exit(1);
  }

  // Lock the page in memory
  // Do this before writing data to the buffer so that any copy-on-write
  // mechanisms will give us our own page locked in memory
  if(mlock(buffer, buf_size) == -1) {
    fprintf(stderr, "Failed to lock page in memory: %s\n", strerror(errno));
    exit(1);
  }

  // Add some data to the memory
  strncpy(buffer, ORIG_BUFFER, strlen(ORIG_BUFFER));

  return buffer;
}

unsigned long get_page_frame_number_of_address(void *addr) {
  // Open the pagemap file for the current process
  FILE *pagemap = fopen("/proc/self/pagemap", "rb");

  // Seek to the page that the buffer is on
  unsigned long offset = (unsigned long)addr / getpagesize() * PAGEMAP_LENGTH;
  if(fseek(pagemap, (unsigned long)offset, SEEK_SET) != 0) {
    fprintf(stderr, "Failed to seek pagemap to proper location\n");
    exit(1);
  }

  // The page frame number is in bits 0-54 so read the first 7 bytes and clear the 55th bit
  unsigned long page_frame_number = 0;
  fread(&page_frame_number, 1, PAGEMAP_LENGTH-1, pagemap);

  page_frame_number &= 0x7FFFFFFFFFFFFF;

  fclose(pagemap);

  return page_frame_number;
}

int open_memory(void) {
  // Open the memory (must be root for this)
  int fd = open("/dev/mem", O_RDWR);

  if(fd == -1) {
    fprintf(stderr, "Error opening /dev/mem: %s\n", strerror(errno));
    exit(1);
  }

  return fd;
}

void seek_memory(int fd, unsigned long offset) {
  unsigned pos = lseek(fd, offset, SEEK_SET);

  if(pos == -1) {
    fprintf(stderr, "Failed to seek /dev/mem: %s\n", strerror(errno));
    exit(1);
  }
}

最佳答案

在函数 get_page_frame_number_of_address 中。 请确认打开文件成功。 FILE *pagemap = fopen("/proc/self/pagemap", "rb");

检查页面映射是否为 NULL。

关于c - 虚拟地址到物理地址的段错误,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35763283/

相关文章:

linux - 无法 scp 到 AWS

mysql - 错误 : 'Can' t connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock' (2)' -- Missing/var/run/mysqld/mysqld. socks

android - 在Android中将文件存储在特定于应用程序的存储中有什么限制?[data/data/{packagename}]?

objective-c - Objective C 中特定对象的内存使用情况

memory - 如何确定 OpenCL 中的可用设备内存?

c++ - 1.#INF00、-1.#IND00 和 -1.#IND 是什么意思?

c - 为什么通知 “abort” 是非法的?

c - 为什么这个程序不产生任何警告?

c - 从 stdin 动态分配行?

linux - 如何在linux shell中将4月1日转换为3月31日