EJTAG: atração para hackers-2


Nas minhas publicações anteriores do EJTAG: uma atração para hackers e Black Swift: usando o EJTAG , foi considerada a maneira mais fácil de usar o EJTAG - carregar na RAM e iniciar um programa do usuário. No entanto, os recursos do EJTAG não se limitam a isso. A publicação descreve como organizar a depuração simples de código usando EJTAG, usando ferramentas openocd e freeware GDB.

Fui solicitado a escrever esta publicação por uma carta de um leitor que me pediu para permanecer anônimo e me pediu ajuda - um dispositivo baseado no AR9344 não inicializa (fica travado no estágio de inicialização do U-boot) - como posso descobrir qual é o problema do EJTAG?
Como eu não tinha um dispositivo baseado no AR9344 em mãos, mas a placa Black Swift Pro baseada no chip AR9331 relacionado acabou sendo a narrativa de olho nele. Eu acho que as mudanças que precisam ser feitas para o AR9344 não são significativas.
Vamos para a declaração do problema: Existe
uma placa Black Swift Pro, à qual nos conectamos usando o openocd via EJTAG e colocamos o processador no modo de parada.
É NECESSÁRIO executar várias dezenas de instruções do processador sequencialmente, parando após cada instrução ser executada e, se necessário, verificando o conteúdo da RAM, ROM de inicialização, registros do controlador periférico ou registros do processador.
Para resolver o problema, vejo pelo menos duas abordagens:
  • simples - apenas usando o openocd - a funcionalidade básica para executar as ações necessárias já está no openocd. Só é necessário poder usá-lo;
  • complexo - usando o pacote openocd + GDB - o usuário controlará o processo de execução das instruções do processador via GDB, e o openocd converterá solicitações GDB em comandos EJTAG.

Agora considere as duas soluções em mais detalhes.
, Black Swift: EJTAG.


1: openocd


Quem lê minhas publicações anteriores sobre o EJTAG deve se lembrar que o openocd aparece neles como um executor de script burro (arquivos de configuração), que parece funcionar no modo em lote e não fornece interação com o usuário. No entanto, não é. De fato, enquanto o software openocd está em execução, é possível "solicitar" que ele execute um comando usando a interface da linha de comandos. Para acessar a interface da linha de comandos, o openocd inicia um servidor telnet.
Por padrão, a porta TCP 4444 será usada para o servidor de telnet.Se necessário, o número da porta TCP pode ser alterado usando a opção telnet_port(veja o exemplo abaixo).
Vamos tentar rastrear o gerenciador de inicialização da placa Black Swift com o openocd.
Exemplo de arquivo de configuraçãoblack-swift-trace.cfg para o openocd, que força o openocd para o servidor de telnet usar a porta 4455:
 fonte [encontre interface / ftdi / tumpa.cfg]
 
 adapter_khz 6000
 
 fonte [encontre black-swift.cfg]
 
 telnet_port 4455
 
 iniciar
 parar

A execução do openocd 0.9.0 como raiz se parece com isso:
 # openocd -f black-swift-trace.cfg
 Open On-Chip Debugger 0.9.0 (28-05-2015: 17)
 Licenciado sob GNU GPL v2
 Para relatórios de erros, leia
         http://openocd.org/doc/doxygen/bugs.html
 nenhum separado
 velocidade do adaptador: 6000 kHz
 Info: seleção automática do primeiro transporte de sessão disponível "jtag". Para substituir, use 'transport selecione <transport>'.
 Erro: nenhum dispositivo encontrado
 Erro: impossível abrir o dispositivo ftdi com vid 0403, pid 8a98, descrição '*' e serial '*'
 Info: velocidade do relógio 6000 kHz
 Informações: tap JTAG: tap / dispositivo ar9331.cpu encontrado: 0x00000001 (mfg: 0x000, parte: 0x0000, ver: 0x0)
 estado de destino: interrompido
 destino interrompido no modo MIPS32 devido a solicitação de depuração, pc: 0xbfc00000
 estado de destino: interrompido
 destino interrompido no modo MIPS32 devido a uma etapa, pc: 0xbfc00404

Agora podemos abrir outra janela do terminal e conectar-se ao servidor openocd telnet usando o programa telnet:
 $ telnet localhost 4455
 Tentando :: 1 ...
 Tentando 127.0.0.1 ...
 Conectado ao localhost.
 O caractere de escape é '^]'.
 Depurador aberto no chip
 >

Uma lista de todos os comandos OpenOCD é fácil de obter com o comando help.
Para execução passo a passo das instruções do processador, o stepseguinte comando é útil para nós :
 passo [endereço]
       executar uma instrução no endereço especificado pelo registro
       contador de comandos (PC). Se o parâmetro address for especificado, então
       A instrução será executada começando com o endereço do endereço.

A execução passo a passo das instruções do processador no console é semelhante a esta:
 > etapa 0xbfc00400
 estado de destino: interrompido
 destino interrompido no modo MIPS32 devido a uma etapa, pc: 0xbfc00404
 > passo
 estado de destino: interrompido
 destino interrompido no modo MIPS32 devido a uma etapa, pc: 0xbfc00408
 > passo
 estado de destino: interrompido
 destino interrompido no modo MIPS32 devido a uma etapa, pc: 0xbfc0040c

Os seguintes comandos openocd também podem ser úteis:
 reg [(número_de_registro | nome_de_registro) [(valor | 'força')]]
       ler ou gravar o valor do registro do processador.
       Chamar reg sem parâmetros resulta na saída de todos os registradores.
       Se o parâmetro 'force' for usado, forçar
       subtraindo o registro do processador (em vez de emitir um cache
       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 no comprimento do endereço do endereço conta bytes.
 
 mdh ['phys'] endereço [contagem]
       o comando é semelhante ao mdb, mas uma palavra de 16 bits é lida em vez de um byte.
 
 mdw ['phys'] endereço [contagem]
       o comando é semelhante ao mdb, mas uma palavra de 32 bits é lida em vez de um byte.

Como você pode ver, infelizmente, a versão mais recente (no momento da redação deste documento) do openocd 0.9.0 não é capaz de desmontar as instruções dos processadores com arquitetura MIPS, embora exista um desmontador para processadores com arquitetura ARM .
A falta de um desmontador torna a execução passo a passo das instruções do processador diretamente com o openocd não muito confortável. Você pode aumentar o nível de conforto se usar o GDB.

Solução 2: use o pacote openocd + GDB


No pacote openocd + GDB, as funções são distribuídas da seguinte forma: o usuário se comunica com o GDB, que fornece uma interface conveniente para depuração, abstraindo do mecanismo pelo qual a execução das instruções é executada, e o openocd assume a tarefa de controlar diretamente o processador de acordo com as instruções do GDB.
O uso do GDB para controlar a execução de instruções em um processador MIPS através do EJTAG tem várias vantagens sobre o openocd:
  • como mencionado acima, um desmontador para a arquitetura MIPS é incorporado ao GDB;
  • é possível usar informações de depuração da fonte; por exemplo, se você estiver depurando seu próprio programa C, o GDB poderá mostrar qual linha de código C está sendo executada no momento e detalhar o estado precisamente das variáveis ​​do programa, e não as células de memória com endereços misteriosos;
  • openocd GDB GDB Remote Serial Protocol; qemu , , — GDB;
  • , GDB TAB.

Deve-se ter em mente que o GDB opera com conceitos de alto nível, e o openocd é forçado a trabalhar com o equipamento que é, e nem sempre, o Wishlist do GDB pode ser efetivamente implementado usando o EJTAG.
Por exemplo, o usuário instrui o GDB a definir um ponto de interrupção no endereço especificado, esta instrução vai para o openocd, mas para os processadores MIPS, o openocd tem pelo menos duas maneiras de definir um ponto de interrupção:
  • sdbbp, , openocd sdbbp , sdbbp , . software breakpoint. , .
  • . . , hardware breakpoint, .

Embora no GDB Remote Serial Protocol haja uma distinção entre o ponto de interrupção de hardware e o ponto de interrupção do software (consulte os pacotes z e z0 na descrição do protocolo ), e o GDB fornece opções apropriadas para escolher o tipo de pontos de interrupção, pode haver restrições no uso de pontos em um processador específico discriminação de um tipo ou de outro. Assim, o openocd tem uma opção gdb_breakpoint_overrideque permite forçar um dos dois métodos descritos para organizar pontos de interrupção.
Para conectar o depurador GDB, implementos openocd um servidor GDB, que por padrão utiliza a porta TCP 3333. Se necessário, o número da porta TCP pode ser alterado usando a opção gdb_port.
Para se conectar ao servidor openocd GDB que controla o processador MIPS, precisamos de uma versão especial do GDB com suporte a MIPS. Suponho que o leitor use computadores baseados em x86 / amd64 executando o Debian Linux para executar o openocd e o GDB, usando mips-linux-gnu-gdb do pacote Sourcery CodeBench. Sobre como instalar este pacote está escrito aqui , você só precisa apresentar a alteração. No momento em que escrevo essas linhas, a última é a versão do Sourcery CodeBench mips-2015.05-18, lançada em maio de 2015, é possível fazer o download do arquivo aqui usando este link .
Vamos tentar usar o pacote openocd + GDB na prática. Execute o openocd:
 # openocd -f execute-u-boot_mod-trace.cfg \
 > -c "gdb_breakpoint_override hard" -c "etapa 0xbfc00400"

Os dois últimos comandos do openocd fornecerão o seguinte:
  • serão utilizados pontos de interrupção de hardware, independentemente do que o GDB imaginar lá (os endereços 0xbfc0xxxx correspondem à ROM, portanto os pontos de interrupção do software não funcionarão);
  • uma instrução será executada a partir do endereço 0xbfc00400, após o que o processador irá parar novamente.

Abra outra janela do terminal e inicie o GDB:
 $ /opt/mips-2015.05/bin/mips-linux-gnu-gdb
 GNU gdb (Sourcery CodeBench Lite 2015.05-18) 7.7.50.20140217-cvs
 Direitos autorais (C) 2014 Free Software Foundation, Inc.
 Licença GPLv3 +: GNU GPL versão 3 ou posterior <http://gnu.org/licenses/gpl.html>
 Este é um software livre: você pode alterá-lo e redistribuí-lo.
 NÃO HÁ GARANTIA, na extensão permitida por lei. Digite "mostrar cópia"
 e "mostrar garantia" para detalhes.
 Este GDB foi configurado como "--host = i686-pc-linux-gnu --target = mips-linux-gnu".
 Digite "show configuration" para obter detalhes da configuração.
 Para obter instruções sobre relatórios de erros, consulte:
 <https://sourcery.mentor.com/GNUToolchain/>.
 Encontre o manual do GDB e outros recursos de documentação on-line em:
 <http://www.gnu.org/software/gdb/documentation/>.
 Para obter ajuda, digite "ajuda".
 Digite "palavra apropriada" para procurar comandos relacionados à "palavra".
 (gdb)

Agora, explicamos ao GDB o tipo de processador com o qual trabalharemos, pedimos para desmontar a próxima instrução executável do processador e, finalmente, conectamos ao servidor openocd GDB:
 (gdb) definir arquitetura mips: isa32r2
 A arquitetura de destino é assumida como mips: isa32r2
 (gdb) definir endian grande
 Presume-se que o alvo seja big endian
 (gdb) defina desmontar a próxima linha em
 (gdb) destino remoto: 3333
 Depuração remota usando: 3333
 0xbfc00404 in ?? ()
 => 0xbfc00404: 40 80 08 00 mtc0 zero, c0_random

Para executar uma instrução de processador no GDB, um comando é usado stepi. Vamos tentar seguir algumas instruções do processador e não deixar o GDB avisá-lo sobre a falta de informações de depuração (não é possível encontrar o início da função); leve essas informações do nada.
 (gdb) stepi
 aviso: o GDB não consegue encontrar o início da função em 0xbfc00408.
 
     O GDB não consegue encontrar o início da função em 0xbfc00408
 e, portanto, não é possível determinar o tamanho do quadro de pilha dessa função.
 Isso significa que o GDB pode não conseguir acessar esse quadro de pilha ou
 os quadros abaixo dele.
     Esse problema provavelmente é causado por um contador de programa inválido ou
 ponteiro de pilha.
     No entanto, se você acha que o GDB deve procurar mais longe
 de 0xbfc00408 para código que se parece com o início de um
 , você pode aumentar o alcance da pesquisa usando o `set
 comando heuristic-fence-post '.
 0xbfc00408 in ?? ()
 => 0xbfc00408: 40 80 10 00 mtc0 zero, c0_entrylo0
 (gdb) stepi
 aviso: o GDB não consegue encontrar o início da função em 0xbfc0040c.
 0xbfc0040c em ?? ()
 => 0xbfc0040c: 40 80 18 00 mtc0 zero, c0_entrylo1
 (gdb) stepi
 aviso: o GDB não consegue encontrar o início da função em 0xbfc00410.
 0xbfc00410 in ?? ()
 => 0xbfc00410: 40 80 20 00 mtc0 zero, c0_context
 (gdb)

Agora leia a palavra de 32 bits em 0xbfc00408:
 (gdb) p / x * 0xbfc00408
 $ 1 = 0x40801000

Para imprimir o estado dos registros do processador, use o comando info registers:
 (gdb) registros de informações
           zero na 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
         status lo oi badvaddr causar pc
       00400004 00244309 b9ca872c ed6a1f00 60808350 bfc00410
           fcsr fir
       00000000 00000000

O GDB é uma ferramenta avançada com um grande número de comandos e opções; para conhecer melhor o GDB, remeto o leitor para a documentação oficial do GDB .

Inicializando o controlador de RAM AR9331


Vamos ver como o GDB pode resolver um problema específico: rastreando a execução do U-boot na placa Black Swift, identificaremos uma sequência de entradas nos registros do controlador RAM, o que leva à sua inicialização. Detectar essa sequência é extremamente útil se queremos executar programas no Black Swift usando o openocd ignorando o U-boot. Além disso, essa sequência de inicialização é útil ao criar um carregador de inicialização alternativo para o Black Swift.
Execute o openocd (como na seção anterior):
 # openocd -f execute-u-boot_mod-trace.cfg \
 > -c "gdb_breakpoint_override hard" -c "etapa 0xbfc00400"

Para executar o GDB, componha um script bs-u-boot-trace-gdb.conf:
 definir arquitetura mips: isa32r2
 definir endian grande
 defina desmontar a próxima linha
 
 alvo remoto: 3333
 
 desativar a paginação
 
 definir arquivo de log bs_gdb.log
 definir logon
 
 while $ pc! = (void (*) ()) 0x9f002ab0
         stepi
         registros de informações
 fim
 
 destacar
 
 Sair

Comparado ao exemplo da seção anterior, esse script faz com que o GDB duplique a saída em um arquivo bs_gdb.loge desativa a paginação (confirmação de virada de página pelo usuário) e começa a executar ciclicamente as instruções do processador e a exibir o estado dos registros do processador após cada instrução. Quando o registro do PC (endereço da instrução a seguir) atinge 0x9f002ab0, o GDB se desconecta do openocd e para de funcionar. Assim, no final do GDB, um arquivo bs_gdb.logcom um rastreamento completo da execução das instruções do processador será criado .
O lançamento do GDB será o seguinte:
 $ /opt/mips-2015.05/bin/mips-linux-gnu-gdb -x bs-u-boot-trace-gdb.conf

Nota: o script bs-u-boot-trace-gdb.confprovavelmente não funcionará imediatamente após a energia ser aplicada à placa, pois o u-boot_mod também redefinirá os erros anti-mistério AR9331, o que fará com que o script pare de executar. Nesse caso, pare o openocd e o GDB e execute o openocd e o GDB novamente.

Agora, tudo é pequeno - você precisa selecionar bs_gdb.logtodas as instruções de gravação do arquivo sw(armazenar palavra, ou seja, escrever um valor de 32 bits). Os registros do controlador de memória AR9331 são de 32 bits, para que outras instruções da família de lojas possam ser ignoradas com segurança.
Como o desmontador produz apenas os nomes dos registros de argumentos da instruçãosw
 => 0xbfc004ec: anúncio f9 00 00 sw t9,0 (t7)

mas não seus valores, não basta bs_gdb.logselecionar todas as linhas que contêm a instrução sw do arquivo . Para determinar qual valor foi gravado em qual endereço usando sw, o arquivo deve ser submetido a bs_gdb.logprocessamento adicional. O processamento pode ser feito usando o 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");
    }
 }


O lançamento parse_gdb_output.plé o seguinte:
$ grep "^ = \ | ^ R" bs_gdb.log | ./parse_gdb_output.pl

Aqui está um trecho de saída parse_gdb_output.pl(as marcas ' <<< PLL' e ' <<< DDR' foram inseridas manualmente posteriormente):
 ...
  0x9f002700: anúncio cf 00 00 sw t7 = {0x00dodom60}, 0 (t6 = {0xb8116248})
  0x9f00271c: anúncio f9 00 00 sw t9 = {0x000fffff}, 0 (t7 = {0xb800009c})
  0x9f0027a0: anúncio 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}) <<<
 ...

Como os endereços dos registros do gerador de sinal de clock (PLL) e os endereços dos registros do controlador de memória do tipo DDR são conhecidos, é fácil descobrir quais números devem ser gravados em quais endereços para inicializar corretamente o controlador de RAM.

Sumário


Como você pode ver, a depuração através do JTAG usando o openocd e o GDB não é nada difícil, e os métodos de trabalho descritos são adequados não apenas para o AR9331, mas após algumas adaptações e até mesmo para processadores com uma arquitetura diferente, para os quais há suporte no openocd e no GDB.

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


All Articles