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: Existeuma 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 step
seguinte 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_override
que 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.log
e 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.log
com 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.conf
provavelmente 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.log
todas 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.log
selecionar 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.log
processamento adicional. O processamento pode ser feito usando o script parse_gdb_output.pl:
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.