¿Qué contiene la dirección física 0 en x86 Linux?

12

No estoy seguro de si esta pregunta debería ir aquí o en reverseengineering.stackexchange.com

Citando de wikipedia :

En el procesador 8086, la tabla de interrupción se llama IVT (tabla de vector de interrupción). El IVT siempre reside en la misma ubicación en la memoria, que va de 0x0000 a 0x03ff, y consta de 256 punteros lejanos en modo real de cuatro bytes (256 × 4 = 1024 bytes de memoria).

Esto es lo que encuentro en el monitor qemu:

(qemu) xp/128xw 0
0000000000000000: 0xf000ff53 0xf000ff53 0xf000e2c3 0xf000ff53
0000000000000010: 0xf000ff53 0xf000ff53 0xf000ff53 0xf000ff53
0000000000000020: 0xf000fea5 0xf000e987 0xf000d62c 0xf000d62c
0000000000000030: 0xf000d62c 0xf000d62c 0xf000ef57 0xf000d62c
0000000000000040: 0xc0005526 0xf000f84d 0xf000f841 0xf000e3fe
0000000000000050: 0xf000e739 0xf000f859 0xf000e82e 0xf000efd2
0000000000000060: 0xf000d648 0xf000e6f2 0xf000fe6e 0xf000ff53
0000000000000070: 0xf000ff53 0xf000ff53 0xf0006aa4 0xc0008930
0000000000000080: 0xf000ff53 0xf000ff53 0xf000ff53 0xf000ff53
0000000000000090: 0xf000ff53 0xf000ff53 0xf000ff53 0xf000ff53
00000000000000a0: 0xf000ff53 0xf000ff53 0xf000ff53 0xf000ff53
00000000000000b0: 0xf000ff53 0xf000ff53 0xf000ff53 0xf000ff53
00000000000000c0: 0xf000ff53 0xf000ff53 0xf000ff53 0xf000ff53
00000000000000d0: 0xf000ff53 0xf000ff53 0xf000ff53 0xf000ff53
00000000000000e0: 0xf000ff53 0xf000ff53 0xf000ff53 0xf000ff53
00000000000000f0: 0xf000ff53 0xf000ff53 0xf000ff53 0xf000ff53
0000000000000100: 0xf000ec59 0xf000ff53 0xf000ff53 0xc0006730
0000000000000110: 0xf000ff53 0xf000ff53 0xf000ff53 0xf000ff53
0000000000000120: 0xf000ff53 0xf000ff53 0xf000ff53 0xf000ff53
0000000000000130: 0xf000ff53 0xf000ff53 0xf000ff53 0xf000ff53
0000000000000140: 0xf000ff53 0xf000ff53 0xf000ff53 0xf000ff53
0000000000000150: 0xf000ff53 0xf000ff53 0xf000ff53 0xf000ff53
0000000000000160: 0xf000ff53 0xf000ff53 0xf000ff53 0xf000ff53
0000000000000170: 0xf000ff53 0xf000ff53 0xf000ff53 0xf000ff53
0000000000000180: 0x00000000 0x00000000 0x00000000 0x00000000
0000000000000190: 0x00000000 0x00000000 0x00000000 0xf000ff53
00000000000001a0: 0xf000ff53 0xf000ff53 0xf000ff53 0xf000ff53
00000000000001b0: 0xf000ff53 0xf000ff53 0xf000ff53 0xf000ff53
00000000000001c0: 0xf000d611 0xf000ec4e 0xf000ec4e 0xf000ec4e
00000000000001d0: 0xf000d61a 0xf000d623 0xf000d608 0xf000ec4e
00000000000001e0: 0xf000ff53 0x00000000 0xf000ff53 0xf000ff53
00000000000001f0: 0xf000ff53 0xf000ff53 0xf000ff53 0xf000ff53

No estoy seguro de qué hacer con esos valores. No se parece a una tabla de descriptores de interrupciones (la desreferenciación de esos valores da todos los valores nulos). Entonces, ¿qué estoy mirando realmente aquí?

Rodeo
fuente

Respuestas:

9

Lo que sea que tu firmware lo haya dejado.

En un sistema moderno ideal, el procesador nunca entra en modo real en absoluto, como lo expliqué en este SU Q&A titulado: ¿En qué modo funcionan las modernas PC con chip Intel de 64 bits en el sector de arranque? , el primer KiB de la memoria física es tan irrelevante como Johan Myréen lo hizo en otra respuesta aquí. Pero muchos firmwares modernos (todavía) tienen soporte de compatibilidad , lo que significa que

  • pueden retroceder (sí, retroceder , dado que pasaron directamente del modo irreal al modo protegido) del modo protegido al modo real para ejecutar programas de sistema escritos para el modo real, como los programas de arranque de PC / AT de estilo antiguo en MBR y VBR; y
  • proporcionan las antiguas API de firmware en modo real y configuran todas las estructuras de datos para esas API, de las que dependen los softwares del sistema antes mencionados.

Una de esas estructuras de datos es el modo real IVT. Las antiguas API de firmware en modo real se basan en intinstrucciones, y el IVT en modo real lo llena el firmware como parte de su inicialización con punteros a las diversas rutinas de manejo de firmware para esas instrucciones.

Los softwares del sistema en modo protegido no necesitan las antiguas API de firmware en modo real, y nunca ejecutan el procesador en modo real, por lo que el IVT en modo real en los primeros 1 KB de memoria física no se utiliza. (El modo protegido v8086 no aborda la dirección física 00000000 y hacia arriba, recuerde. Dirige las direcciones lógicas 00000000 y hacia arriba, que se traducen por tablas de páginas). En los sistemas EFI modernos, el firmware entrega un mapa de memoria de la memoria física al sistema operativo bootstrap, que le indica qué partes están reservadas al firmware para sus propios fines de API de modo protegido, y qué partes el sistema operativo es libre de seguir y usar para su grupo de memoria física. En teoría, la primera página de la memoria física puede estar en la última categoría.

En la práctica, en primer lugar, los firmwares a menudo marcan la primera página de la memoria física como "código de servicios de arranque", lo que significa que un sistema operativo puede reclamarlo y simplemente seguir adelante y usarlo como parte de su grupo de memoria física, pero solo después del arranque. El sistema operativo ha cerrado los servicios de tiempo del firmware EFI y el firmware se ha reducido a proporcionar solo sus servicios de tiempo de ejecución. Un ejemplo de esto se puede ver en el registro del kernel de Linux (con la add_efi_memmapopción) que muestra Finnbarr P. Murphy:

[0.000000] efi: mem00: type = 3, attr = 0xf, range = [0x0000000000000000-0x0000000000001000) (0MB)
que xe decodifica con otro programa en una forma más legible para humanos como:

[# 00] Tipo: EfiBootServicesCode Attr: 0xF
      Phys: 0000000000000000-0000000000001000
      Virt: 0000000000000000-0000000000001000

En la práctica, en segundo lugar, Linux ignora explícitamente este rango de memoria física incluso si el firmware dice que puede seguir adelante y usarlo. Encontrará que tanto en firmwares EFI como no EFI, una vez que Linux tiene el mapa de memoria física, lo parchea ( en una función llamadatrim_bios_range ), lo que resulta en mensajes de registro del kernel como:

[0.000000] e820: actualización [mem 0x00000000-0x00000fff] usable ==> reservado

Esto no es tanto para hacer frente a los firmwares EFI modernos, donde el IVT en modo real no es parte de la API de firmware, sino para hacer frente a los antiguos firmwares PC98, donde es parte de la API de firmware pero los firmwares lo informan (a través de esa misma API) como memoria física disponible para ser sobrescrita alegremente por el sistema operativo.

Entonces, mientras que en teoría ese rango de memoria física podría contener código o datos arbitrarios, dependiendo de las necesidades momentáneas de los asignadores de memoria del núcleo y la memoria virtual paginada por demanda; en la práctica, Linux simplemente lo deja intacto ya que el firmware lo configuró originalmente.

Y en su sistema, el firmware lo había llenado con entradas IVT en modo real. Las entradas IVT en modo real son solo 16:16 punteros lejanos, por supuesto, y si miras tu memoria usando un hexdump de 2 bytes, puedes ver esto con bastante claridad. Algunos ejemplos:

  • La mayoría de sus entradas IVT apuntan a F000: FF53, una dirección en el área ROM del firmware en modo real. Probablemente sea una rutina ficticia que no hace más que un iret.
  • La entrada IVT 1E apunta a F000: 6AA4, una tabla en esa misma área ROM.
  • La entrada IVT 1F apunta a C000: 8930, una tabla en el área de firmware de la ROM de video en modo real.
  • La entrada IVT 43 apunta a C000: 6730, otra tabla en el área de firmware de ROM de video en modo real.

Otras lecturas

JdeBP
fuente
No, me refiero a lo que escribí. Manual de desarrolladores de software de arquitectura Intel volumen 3 capítulo 20 § 2.
JdeBP
Bueno, ahora lo tienes, porque lo es; como explica la primera oración de esa sección. Sospecho de esto que el no reconocimiento de la abreviatura común "v8086" es una especie de shibboleth. (-:
JdeBP
Necesita aprender a leer sustantivos atributivos. O bien, aprender a vivir sin sopa de champiñones.
JdeBP
7

La arquitectura del procesador 8086 original (implementada como modo real en procesadores 80286+) no tiene relevancia para Linux, que opera en modo protegido. No hay una tabla de vectores de interrupción en la dirección física 0, en su lugar se utiliza una tabla de descriptores de interrupción que contiene descriptores de interrupción. El IDT puede ubicarse en cualquier lugar de la memoria.

El kernel de Linux obtiene un mapa de memoria física del firmware (BIOS o EFI) que indica qué marcos de página de memoria física son utilizables y cuáles están reservados o no. El rango de marcos de página utilizables no es contiguo, pero generalmente tiene enormes agujeros. Tradicionalmente, el núcleo Linux x86 ha omitido el inicio de la memoria física, incluso si está marcado como utilizable. Por lo tanto, la dirección física 0 no es utilizada por el kernel de Linux.

Johan Myréen
fuente
Esto tiene sentido. ¿Alguna idea de qué es el contenido sobrante en esa página no utilizada?
rhodeo
Buscar en Google 53 ffrevela que es muy probable que se trate de una tabla de vectores de interrupción en modo real 8086 configurada por el firmware o un cargador de arranque.
Johan Myréen
4

Volcar la memoria

Aquí hay una forma alternativa de volcar el contenido de la memoria dentro del sistema frente a tener que hacerlo externamente:

$ head /dev/mem | hexdump -C
00000000  53 ff 00 f0 53 ff 00 f0  53 ff 00 f0 53 ff 00 f0  |S...S...S...S...|
00000010  53 ff 00 f0 53 ff 00 f0  cc e9 00 f0 53 ff 00 f0  |S...S.......S...|
00000020  a5 fe 00 f0 87 e9 00 f0  53 ff 00 f0 46 e7 00 f0  |........S...F...|
00000030  46 e7 00 f0 46 e7 00 f0  57 ef 00 f0 53 ff 00 f0  |F...F...W...S...|
00000040  22 00 00 c0 4d f8 00 f0  41 f8 00 f0 fe e3 00 f0  |"...M...A.......|
00000050  39 e7 00 f0 59 f8 00 f0  2e e8 00 f0 d4 ef 00 f0  |9...Y...........|
00000060  a4 f0 00 f0 f2 e6 00 f0  6e fe 00 f0 53 ff 00 f0  |........n...S...|
00000070  ed ef 00 f0 53 ff 00 f0  c7 ef 00 f0 ed 57 00 c0  |....S........W..|
00000080  53 ff 00 f0 53 ff 00 f0  53 ff 00 f0 53 ff 00 f0  |S...S...S...S...|
...
...
000afea0  00 00 00 00 00 00 00 00  aa aa aa 00 aa aa aa 00  |................|
000afeb0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
000b0000  ff ff ff ff ff ff ff ff  ff ff ff ff ff ff ff ff  |................|
*
000c0000  55 aa 40 e9 62 0a 00 00  00 00 00 00 00 00 00 00  |[email protected]...........|
000c0010  00 00 00 00 00 00 00 00  00 00 00 00 00 00 49 42  |..............IB|

Análisis

La parte superior por encima de 000c0000 podría estar relacionada con el gestor de arranque. ¿Por qué sospecharía esto? El código 55aah en la ubicación 000c0000generalmente puede ser una marca en la memoria para cosas como un disparador para que el BIOS ejecute un cargador de arranque secundario.

Referencia: Boot Signature - BIOS

  ss # 1

Sin embargo, dado que este 55aah ocurre en el rango c0000h-effffh, es más probable que esta porción sea el encabezado de expansión PNP:

Referencia: especificación de arranque del BIOS

3.3 Dispositivos con encabezados de expansión PnP

Todos los dispositivos IPL con ROM de opción deben contener un encabezado de ROM de opción válido que se encuentre entre las direcciones de memoria del sistema C0000h y EFFFFh en un límite de 2k y comience con 55AAh. El arranque de un dispositivo solo se puede controlar si tiene un encabezado de expansión PnP. El encabezado de expansión, cuya dirección reside dentro del encabezado ROM de opción estándar en offset + 1Ah, contiene información importante utilizada para configurar el dispositivo. También contiene punteros para codificar en la ROM de opción del dispositivo (BCV o BEV) que el BIOS llamará para arrancar desde el dispositivo. Consulte el Apéndice A para ver la estructura del encabezado de expansión PnP. Hay dos formas de iniciar un dispositivo IPL con un encabezado de expansión PnP. Debe contener un BCV o un BEV.

53ff ...

En cuanto a los datos de 53ffh que están al principio. No me queda claro qué es eso en realidad. Investigando más a fondo es probable que sea algo que el kernel de Linux escribió allí después de que la carga de arranque del BIOS del MBR se transfiriera al kernel de Linux para arrancar.

Por lo general, el gestor de arranque cargará el kernel en la memoria y luego saltará al kernel. El núcleo podrá reclamar la memoria utilizada por el gestor de arranque (porque ya ha realizado su trabajo). Sin embargo, es posible incluir el código del sistema operativo dentro del sector de arranque y mantenerlo residente después de que comience el sistema operativo

Excavando más, pude encontrar este párrafo de un trabajo de investigación titulado: Inyección de código malicioso a través de / dev / mem :

1 El dispositivo mem

/ dev / mem es la interfaz del controlador para la memoria físicamente direccionable. La intención original de mem y kmem era ayudar a depurar el kernel. Podemos usar el dispositivo como un dispositivo de caracteres normal, usando lseek () para seleccionar un desplazamiento de dirección. El dispositivo kmem es similar pero proporciona una imagen de la memoria del núcleo en el contexto del direccionamiento virtual. El servidor Xorg utiliza el dispositivo mem para acceder a la memoria de video VESA, así como a la Tabla de vectores de interrupción ROM ROM (IVT) ubicada en la dirección física 0x00000000 para manipular los modos de video en modo VM86. DOSEMU también usa esto para acceder al BIOS IVT para poder realizar interrupciones del BIOS para diversas tareas (lecturas de disco, impresión en la consola, etc.).

Referencias

slm
fuente