EJTAG: atracción para hackers-2


En mis publicaciones anteriores de EJTAG: una atracción para los hackers y Black Swift: usando EJTAG , se consideró la forma más fácil de usar EJTAG: cargar en RAM e iniciar un programa de usuario. Sin embargo, las capacidades de EJTAG no se limitan a esto. La publicación describe cómo organizar la depuración de código simple usando EJTAG, usando herramientas gratuitas de openocd y GDB.

Se me solicitó que escribiera esta publicación mediante una carta de un lector que me pidió permanecer en el anonimato y que me pidió ayuda, un dispositivo basado en AR9344 no se inicia (se cuelga en la etapa de inicialización de U-boot), ¿cómo puedo averiguar cuál es el problema con EJTAG?
Dado que no tenía un dispositivo basado en el AR9344 a mano, pero la placa Black Swift Pro basada en el chip AR9331 relacionado resultó ser la narrativa con la vista puesta. Creo que los cambios que deben hacerse para el AR9344 no son significativos.
Pasemos a la declaración del problema: hay
una placa Black Swift Pro, a la que nos conectamos usando openocd a través de EJTAG y ponemos el procesador en modo de parada.
SE REQUIERE ejecutar varias decenas de instrucciones de procesador secuencialmente, deteniéndose después de que se ejecute cada instrucción y, si es necesario, verificando el contenido de RAM, ROM de arranque, registros de controlador periférico o registros de procesador.
Para resolver el problema, veo al menos dos enfoques:
  • simple - solo usando openocd - la funcionalidad básica para realizar las acciones requeridas ya está en openocd. Solo es necesario para poder usarlo;
  • complejo - usando el paquete openocd + GDB - el usuario controlará el proceso de ejecución de instrucciones del procesador a través de GDB, y openocd convertirá las solicitudes GDB en comandos EJTAG.

Ahora considere ambas soluciones con más detalle.
, Black Swift: EJTAG.


1: openocd


Quienes lean mis publicaciones anteriores sobre EJTAG deben recordar que openocd aparece en ellos como un ejecutor de script tonto (archivos de configuración), que parece funcionar en modo por lotes y no proporciona la interacción del usuario. Sin embargo, no lo es. De hecho, mientras el software openocd se está ejecutando, es posible "pedirle" que ejecute un comando usando la interfaz de línea de comando. Para acceder a la interfaz de línea de comandos, openocd inicia un servidor telnet.
De manera predeterminada, se utilizará el puerto TCP 4444 para el servidor telnet. Si es necesario, el número de puerto TCP se puede cambiar mediante la opción telnet_port(consulte el ejemplo a continuación).
Intentemos rastrear el gestor de arranque de la placa Black Swift con openocd.
Ejemplo de archivo de configuraciónblack-swift-trace.cfg para openocd, que obliga a openocd para que el servidor telnet use el puerto 4455:
 fuente [buscar interfaz / ftdi / tumpa.cfg]
 
 adapter_khz 6000
 
 fuente [encontrar black-swift.cfg]
 
 telnet_port 4455
 
 en eso
 detener

Ejecutando openocd 0.9.0 como root se ve así:
 # openocd -f black-swift-trace.cfg
 Open On-Chip Debugger 0.9.0 (2015-05-28-17: 08)
 Licenciado bajo GNU GPL v2
 Para informes de errores, lea
         http://openocd.org/doc/doxygen/bugs.html
 ninguno separado
 velocidad del adaptador: 6000 kHz
 Información: auto-seleccionando el primer transporte de sesión disponible "jtag". Para anular el uso de 'transporte, seleccione <transport>'.
 Error: no se encontró ningún dispositivo
 Error: no se puede abrir el dispositivo ftdi con vid 0403, pid 8a98, descripción '*' y serial '*'
 Información: velocidad de reloj 6000 kHz
 Información: JTAG tap: ar9331.cpu tap / dispositivo encontrado: 0x00000001 (mfg: 0x000, parte: 0x0000, ver: 0x0)
 estado objetivo: detenido
 objetivo detenido en modo MIPS32 debido a solicitud de depuración, pc: 0xbfc00000
 estado objetivo: detenido
 objetivo detenido en modo MIPS32 debido a un solo paso, pc: 0xbfc00404

Ahora podemos abrir otra ventana de terminal y conectarnos al servidor telnet openocd usando el programa telnet:
 $ telnet localhost 4455
 Intentando :: 1 ...
 Intentando 127.0.0.1 ...
 Conectado a localhost.
 El carácter de escape es '^]'.
 Abrir depurador en chip
 >

Una lista de todos los comandos de openocd es fácil de obtener con el comando help.
Para la ejecución paso a paso de las instrucciones del procesador, el stepsiguiente comando es útil para nosotros :
 paso [dirección]
       ejecutar una instrucción en la dirección especificada por el registro
       contador de comandos (PC). Si se especifica el parámetro de dirección, entonces
       La instrucción se ejecutará comenzando con la dirección de la dirección.

La ejecución paso a paso de las instrucciones del procesador en la consola se ve así:
 > paso 0xbfc00400
 estado objetivo: detenido
 objetivo detenido en modo MIPS32 debido a un solo paso, pc: 0xbfc00404
 > paso
 estado objetivo: detenido
 objetivo detenido en modo MIPS32 debido a un solo paso, pc: 0xbfc00408
 > paso
 estado objetivo: detenido
 objetivo detenido en modo MIPS32 debido a un solo paso, pc: 0xbfc0040c

Los siguientes comandos de openocd también pueden ser útiles:
 reg [(número_registro | nombre_registro) [(valor | 'fuerza')]]
       leer o escribir el valor del registro del procesador.
       Llamar a reg sin parámetros da como resultado la salida de todos los registros.
       Si se usa el parámetro 'force', forzado
       restando el registro del procesador (en lugar de emitir un caché
       valores).
 
 mwb ['phys'] address value [count]
          address ,     value.
          phys,   address —  ,
          — .
          count    address  
           count,    
         value.
 
 mwh ['phys'] address value [count]
         mwb,     16- .
 
 mww ['phys'] address value [count]
         mwb,     32- .
 
 mdb ['phys'] address [count]
               address.
          phys,   address —  ,
          — .
          count        
       matriz en la dirección dirección longitud recuento bytes.
 
 mdh ['phys'] dirección [cuenta]
       el comando es similar a mdb, pero se lee una palabra de 16 bits en lugar de un byte.
 
 mdw ['phys'] dirección [cuenta]
       el comando es similar a mdb, pero se lee una palabra de 32 bits en lugar de un byte.

Como puede ver, desafortunadamente, la última versión (al momento de escribir este artículo) de openocd 0.9.0 no puede desarmar las instrucciones del procesador con la arquitectura MIPS, aunque existe un desensamblador para procesadores con arquitectura ARM .
La falta de un desensamblador hace que la ejecución paso a paso de las instrucciones del procesador directamente con openocd no sea muy cómoda. Puede aumentar el nivel de comodidad si usa GDB.

Solución 2: use el paquete openocd + GDB


En el paquete openocd + GDB, los roles se distribuyen de la siguiente manera: el usuario se comunica con GDB, que proporciona una interfaz conveniente para la depuración, abstracción del mecanismo mediante el cual se lleva a cabo la ejecución de instrucciones, y openocd asume la tarea de controlar directamente el procesador de acuerdo con las instrucciones de GDB.
Usar GDB para controlar la ejecución de instrucciones en un procesador MIPS a través de EJTAG tiene varias ventajas sobre openocd:
  • como se mencionó anteriormente, un desensamblador para la arquitectura MIPS está integrado en GDB;
  • es posible usar información de depuración de la fuente; por ejemplo, si está depurando su propio programa C, entonces GDB podrá mostrar qué línea de código C se está ejecutando actualmente y detallar el estado de las variables del programa, y ​​no las celdas de memoria con direcciones misteriosas;
  • openocd GDB GDB Remote Serial Protocol; qemu , , — GDB;
  • , GDB TAB.

Debe tenerse en cuenta que GDB opera con conceptos de alto nivel, y openocd se ve obligado a trabajar con el equipo que es y no siempre GDB Wishlist puede implementarse de manera efectiva usando EJTAG.
Por ejemplo, el usuario le indica a GDB que establezca un punto de interrupción en la dirección especificada, esta instrucción va a openocd, pero para los procesadores MIPS, openocd tiene al menos dos formas de establecer un punto de interrupción:
  • sdbbp, , openocd sdbbp , sdbbp , . software breakpoint. , .
  • . . , hardware breakpoint, .

Aunque dentro del Protocolo serie remoto GDB existe una distinción entre el punto de interrupción de hardware y el punto de interrupción de software (consulte los paquetes z y z0 en la descripción del protocolo ), y GDB proporciona las opciones apropiadas para elegir el tipo de puntos de interrupción, puede resultar que haya restricciones en el uso de puntos en un procesador en particular desglose de un tipo u otro. En consecuencia, openocd tiene una opción gdb_breakpoint_overrideque le permite forzar uno de los dos métodos descritos para organizar los puntos de interrupción.
Para conectar el depurador GDB, openocd implementa un servidor GDB, que por defecto usa el puerto TCP 3333. Si es necesario, el número de puerto TCP se puede cambiar usando la opción gdb_port.
Para conectarnos al servidor GDB openocd que controla el procesador MIPS, necesitamos una versión especial de GDB con soporte MIPS. Supongo que el lector usa computadoras basadas en x86 / amd64 que ejecutan Debian Linux para ejecutar openocd y GDB, usando mips-linux-gnu-gdb del paquete Sourcery CodeBench. Sobre cómo instalar este paquete está escrito aquí , solo necesita introducir la enmienda, que al momento de escribir estas líneas, la última es la versión de Sourcery CodeBench mips-2015.05-18, lanzada en mayo de 2015, puede descargar el archivo aquí usando este enlace .
Intentemos usar el paquete openocd + GDB en la práctica. Ejecute openocd:
 # openocd -f run-u-boot_mod-trace.cfg \
 > -c "gdb_breakpoint_override hard" -c "paso 0xbfc00400"

Los dos últimos comandos para openocd proporcionarán lo siguiente:
  • se utilizarán puntos de interrupción de hardware, independientemente de lo que GDB imagine allí (las direcciones 0xbfc0xxxx corresponden a ROM, por lo que los puntos de interrupción de software no funcionarán);
  • se ejecutará una instrucción desde la dirección 0xbfc00400, después de lo cual el procesador se detendrá nuevamente.

Abra otra ventana de terminal e inicie GDB:
 $ /opt/mips-2015.05/bin/mips-linux-gnu-gdb
 GNU gdb (Sourcery CodeBench Lite 2015.05-18) 7.7.50.20140217-cvs
 Copyright (C) 2014 Free Software Foundation, Inc.
 Licencia GPLv3 +: GNU GPL versión 3 o posterior <http://gnu.org/licenses/gpl.html>
 Esto es software libre: usted es libre de cambiarlo y redistribuirlo.
 NO HAY GARANTÍA, en la medida permitida por la ley. Escriba "mostrar copia"
 y "mostrar garantía" para más detalles.
 Este GDB se configuró como "--host = i686-pc-linux-gnu --target = mips-linux-gnu".
 Escriba "show configuration" para obtener detalles de la configuración.
 Para obtener instrucciones de informe de errores, consulte:
 <https://sourcery.mentor.com/GNUToolchain/>.
 Encuentre el manual de GDB y otros recursos de documentación en línea en:
 <http://www.gnu.org/software/gdb/documentation/>.
 Para obtener ayuda, escriba "ayuda".
 Escriba "apropos word" para buscar comandos relacionados con "word".
 (gdb)

Ahora le explicamos a GDB el tipo de procesador con el que trabajaremos, le pedimos que desarme la siguiente instrucción ejecutable del procesador y finalmente nos conectemos al servidor GDB de openocd:
 (gdb) establecer mips de arquitectura: isa32r2
 Se supone que la arquitectura de destino es mips: isa32r2
 (gdb) establecer endian grande
 Se supone que el objetivo es big endian
 (gdb) establezca desmontar-siguiente-línea en
 (gdb) control remoto objetivo: 3333
 Depuración remota utilizando: 3333
 0xbfc00404 en ?? ()
 => 0xbfc00404: 40 80 08 00 mtc0 cero, c0_random

Para ejecutar una instrucción de procesador en GDB, se utiliza un comando stepi. Intentemos seguir algunas instrucciones del procesador y no permita que GDB le advierta sobre la falta de información de depuración (no puede encontrar el inicio de la función), tome esta información de la nada.
 (gdb) stepi
 advertencia: GDB no puede encontrar el inicio de la función en 0xbfc00408.
 
     GDB no puede encontrar el inicio de la función en 0xbfc00408
 y por lo tanto no puede determinar el tamaño del marco de la pila de esa función.
 Esto significa que GDB puede no poder acceder a ese marco de pila, o
 Los marcos debajo de él.
     Este problema probablemente sea causado por un contador de programa no válido o
 puntero de pila.
     Sin embargo, si cree que GDB debería simplemente buscar más atrás
 de 0xbfc00408 para código que parece el comienzo de un
 función, puede aumentar el rango de búsqueda usando el `set
 comando 'heuristic-fence-post'.
 0xbfc00408 en ?? ()
 => 0xbfc00408: 40 80 10 00 mtc0 cero, c0_entrylo0
 (gdb) stepi
 advertencia: GDB no puede encontrar el inicio de la función en 0xbfc0040c.
 0xbfc0040c en ?? ()
 => 0xbfc0040c: 40 80 18 00 mtc0 cero, c0_entrylo1
 (gdb) stepi
 advertencia: GDB no puede encontrar el inicio de la función en 0xbfc00410.
 0xbfc00410 en ?? ()
 => 0xbfc00410: 40 80 20 00 mtc0 cero, c0_context
 (gdb)

Ahora lea la palabra de 32 bits en 0xbfc00408:
 (gdb) p / x * 0xbfc00408
 $ 1 = 0x40801000

Para imprimir el estado de los registros del procesador, use el comando info registers:
 (gdb) registros de información
           cero en v0 v1 a0 a1 a2 a3
  R0 00000000 37c688e2 22b15a00 28252198 0c12d319 4193c014 84e49102 06193640
             t0 t1 t2 t3 t4 t5 t6 t7
  R8 00000002 9f003bc0 92061301 1201c163 31d004a0 92944911 ac031248 b806001c
             s0 s1 s2 s3 s4 s5 s6 s7
  R16 8bc81985 402da011 c94d2454 88d5a554 81808e0d cc445151 4401a826 50020402
             t8 t9 k0 k1 gp sp s8 ra
  R24 01c06b30 01000000 10000004 fffffffe 9f003bc0 54854eab 329d626b bfc004b4
         estado lo hola badvaddr causa pc
       00400004 00244309 b9ca872c ed6a1f00 60808350 bfc00410
           abeto fcsr
       00000000 00000000

GDB es una herramienta avanzada con una gran cantidad de comandos y opciones; Para conocer mejor a GDB, remito al lector a la documentación oficial de GDB .

Inicializando el Controlador RAM AR9331


Veamos cómo GDB puede resolver un problema específico: al rastrear la ejecución de U-boot en el tablero Black Swift, identificaremos una secuencia de entradas en los registros del controlador RAM, lo que conduce a su inicialización. La detección de dicha secuencia es extremadamente útil si queremos ejecutar programas en Black Swift usando openocd sin pasar por U-boot. Además, esta secuencia de inicialización es útil al crear un gestor de arranque alternativo para Black Swift.
Ejecute openocd (como en la sección anterior):
 # openocd -f run-u-boot_mod-trace.cfg \
 > -c "gdb_breakpoint_override hard" -c "paso 0xbfc00400"

Para ejecutar GDB, redacte un script bs-u-boot-trace-gdb.conf:
 establecer mips de arquitectura: isa32r2
 establecer endian grande
 establecer desmonte-next-line en
 
 control remoto objetivo: 3333
 
 desactivar la paginación
 
 establecer el archivo de registro bs_gdb.log
 establecer inicio de sesión
 
 while $ pc! = (void (*) ()) 0x9f002ab0
         stepi
         registros de información
 final
 
 despegar
 
 dejar

En comparación con el ejemplo de la sección anterior, esta secuencia de comandos hace que GDB duplique la salida en un archivo bs_gdb.log, y deshabilita la paginación (confirmación del cambio de página por parte del usuario), y luego comienza a ejecutar cíclicamente las instrucciones del procesador y muestra el estado de los registros del procesador después de cada instrucción. Cuando el registro de PC (registro de dirección de las siguientes instrucciones) alcanza 0x9f002ab0, GDB se desconecta de openocd y deja de funcionar. Por lo tanto, al final de GDB, se creará un archivo bs_gdb.logcon un seguimiento completo de la ejecución de las instrucciones del procesador.
El lanzamiento de GDB será el siguiente:
 $ /opt/mips-2015.05/bin/mips-linux-gnu-gdb -x bs-u-boot-trace-gdb.conf

Nota: lo bs-u-boot-trace-gdb.confmás probable es que la secuencia de comandos no funcione inmediatamente después de aplicar la alimentación a la placa, ya que u-boot_mod también restablecerá los errores anti-misterio AR9331, lo que hará que la secuencia de comandos deje de ejecutarse. En este caso, detenga openocd y GDB, y luego ejecute openocd y GDB nuevamente.

Ahora se trata de lo pequeño: debe seleccionar bs_gdb.logtodas las instrucciones de escritura del archivo sw(almacenar palabra, es decir, escribir un valor de 32 bits). Los registros del controlador de memoria AR9331 son de 32 bits, por lo que otras instrucciones de la familia de tiendas se pueden ignorar de forma segura.
Dado que el desensamblador produce solo los nombres de los registros de argumentos de la instrucciónsw
 => 0xbfc004ec: ad f9 00 00 sw t9,0 (t7)

pero no sus valores, no es suficiente bs_gdb.logseleccionar todas las líneas que contienen la declaración sw del archivo . Para determinar qué valor se escribió en qué dirección utilizando sw, el archivo debe estar sujeto a bs_gdb.logun procesamiento adicional. El procesamiento se puede hacer usando el script parse_gdb_output.pl:
 #!/usr/bin/perl -w
 
 my %r;
 
 foreach $i (qw(zero at v0 v1 a0 a1 a2 a3 t0 t1 t2 t3 t4 t5 t6 t7
        s0 s1 s2 s3 s4 s5 s6 s7 t8 t9 k0 k1 gp sp s8 ra)) {
    $r{$i} = "none";
 }
 
 sub parse_reg($)
 {
    $_ = $_[0];
    if (/^ R/) {
        my @fields = split m'\s+';
        my $f = 2;
        my @rgs;
 
        @rgs = qw(zero at v0 v1 a0 a1 a2 a3) if (/^ R0/);
        @rgs = qw(t0 t1 t2 t3 t4 t5 t6 t7) if (/^ R8/);
        @rgs = qw(s0 s1 s2 s3 s4 s5 s6 s7) if (/^ R1/);
        @rgs = qw(t8 t9 k0 k1 gp sp s8 ra) if (/^ R2/);
 
        foreach $i (@rgs) {
            $r{$i} = $fields[$f];
            $f = $f + 1;
        }
    }
 }
 
 while (<>) {
    if (/^=>([^s]*)\tsw\t([^,]*),(\d+)\(([^)]*)\)/) {
        my $rs = $2;
        my $offset = $3;
        my $rd = $4;
 
        parse_reg(<>);
        parse_reg(<>);
        parse_reg(<>);
        parse_reg(<>);
 
        print("$1    sw $rs={0x$r{$rs}}, $offset($rd={0x$r{$rd}})\n");
    }
 }


El lanzamiento parse_gdb_output.ples el siguiente:
$ grep "^ = \ | ^ R" bs_gdb.log | ./parse_gdb_output.pl

Aquí hay un fragmento de salida parse_gdb_output.pl(las marcas ' <<< PLL' y ' <<< DDR' se ingresaron manualmente más tarde):
 ...
  0x9f002700: ad cf 00 00 sw t7 = {0x00dicsoft60}, 0 (t6 = {0xb8116248})
  0x9f00271c: ad f9 00 00 sw t9 = {0x000fffff}, 0 (t7 = {0xb800009c})
  0x9f0027a0: ad f9 00 00 sw t9 = {0x00018004}, 0 (t7 = {0xb8050008}) <<< PLL
  0x9f0027dc:   ad f9 00 00    sw t9={0x00000352}, 0(t7={0xb8050004}) <<<
  0x9f002840:   ad f9 00 00    sw t9={0x40818000}, 0(t7={0xb8050000}) <<<
  0x9f002898:   ad f9 00 00    sw t9={0x001003e8}, 0(t7={0xb8050010}) <<<
  0x9f0028f4:   ad f9 00 00    sw t9={0x00818000}, 0(t7={0xb8050000}) <<<
  0x9f002970:   ad cf 00 00    sw t7={0x00800000}, 0(t6={0xb8116248})
 ...
  0x9f002994:   ad cf 00 00    sw t7={0x40800700}, 0(t6={0xb8116248})
  0x9f002a54:   ad f9 00 00    sw t9={0x00008000}, 0(t7={0xb8050008})
  0x9f00309c:   af 38 00 00    sw t8={0x7fbc8cd0}, 0(t9={0xb8000000}) <<< DDR
  0x9f0030b0:   af 38 00 00    sw t8={0x9dd0e6a8}, 0(t9={0xb8000004}) <<<
  0x9f0030dc:   af 38 00 00    sw t8={0x00000a59}, 0(t9={0xb800008c}) <<<
  0x9f0030ec: af 38 00 00 sw t8 = {0x00000008}, 0 (t9 = {0xb8000010}) <<<
 ...

Dado que se conocen las direcciones de los registros del generador de señal de reloj (PLL) y las direcciones de los registros del controlador de memoria del tipo DDR, es fácil averiguar qué números deben escribirse en qué direcciones para inicializar correctamente el controlador RAM.

Resumen


Como puede ver, la depuración a través de JTAG usando openocd y GDB no es nada difícil, y los métodos de trabajo descritos son adecuados no solo para AR9331, sino, después de algunas adaptaciones, e incluso para procesadores con una arquitectura diferente, para la cual hay soporte en openocd y GDB.

Source: https://habr.com/ru/post/es383505/


All Articles