Dependendo de sua experiência anterior com linguagens de programação e ecossistemas, a depuração pode ser algo que você nunca faz ou uma fixação absoluta do seu processo de desenvolvimento.
Por exemplo, no Java ( Kotlin e outro ecossistema de tecnologia baseado em JVM, devido à sua longa história de ferramentas sofisticadas, muitas pessoas (incluindo eu) dependem de um depurador em seu ciclo normal de desenvolvimento. Em muitas linguagens digitadas dinamicamente, esse fluxo de trabalho não é amplamente adotado.
Essas são generalizações, é claro. Quase toda linguagem de programação tem algum mecanismo de depuração, mas o fato de os desenvolvedores usarem depuradores ou não parece depender da qualidade e usabilidade das ferramentas, bem como das tarefas nas quais estão trabalhando.
Em qualquer caso, ter uma boa história para depuração é uma parte crucial do processo de desenvolvimento. Neste tutorial Rust GDB, mostraremos como depurar aplicativos Rust usando uma das melhores ferramentas de depuração Rust disponíveis: o Projeto GNU Debugger (GDB) .
Abordaremos o seguinte:
- O que é GDB?
- Configurando GDB no Rust
- O que é
rust-gdb
-
rust-gdb
exemplo - Layouts e estado de inspeção
- Manipulação de estado e pontos de controle
- Depuração de um aplicativo de rede assíncrono
O que é GDB?
O GNU Project Debugger (GDB) é um programa muito antigo escrito por Richard Stallman , o autoproclamado “Chefe GNUisance do Projeto GNU , ”Em 1986. GDB tem suporte para várias linguagens, como C/C ++, mas também linguagens modernas, como Go and Rust .
GDB é um aplicativo de linha de comando, mas há muitos front-ends de GUI e integrações IDE disponíveis para ele. Uma implementação moderna baseada em navegador é gdbgui , por exemplo. Neste tutorial, vamos nos ater à interface de linha de comando porque ela funciona em qualquer lugar, não precisa de dependências externas e é simples de usar para o que estamos tentando realizar.
GDB roda em Linux, MacOS e Windows e vem pré-instalado na maioria das distros Linux comuns. Você pode verificar o GDB documentação para as instruções de instalação de sua plataforma.
O GDB é extremamente complexo e poderoso, por isso não entraremos em detalhes sobre o GDB neste tutorial. Manteremos a funcionalidade básica, como definir pontos de interrupção, executar um programa, percorrê-lo, imprimir variáveis etc.
Configurando GDB em Rust
Para acompanhar, tudo o que você precisa é de uma instalação razoavelmente recente do Rust (1.39+) e uma instalação recente do GDB (8.x +). Uma ferramenta para enviar pacotes TCP como netcat
também pode ser útil.
Além disso, certifique-se de que haja um executável rust-gdb
na mesma pasta que seu executável rustc
. Se você instalar e atualizar o Rust usando Rustup , esse deve ser o caso por padrão.
Primeiro, crie um novo projeto Rust:
cargo novo rust-gdb-example cd rust-gdb-example
Em seguida, edite o arquivo Cargo.toml
e adicione as dependências necessárias.
[dependências] tokio={version="1.1", features=["full"]}
Nesse caso, nós apenas adicionamos Tokio como uma dependência, já que construiremos um exemplo de TCP assíncrono muito básico para mostrar que podemos depurar funções assíncronas da mesma forma que as”normais”.
Adicione o seguinte código a src/lib.rs
:
# [derivar (Clonar, Depurar)] pub enum AnimalType { Gato, Cachorro, } # [derivar (Clonar, Depurar)] pub struct Animal { tipo de pub: AnimalType, nome do pub: String, pub age: usize, } # [derivar (Clonar, Depurar)] pub struct Person { nome do pub: String, animais de estimação de pub: Vec & lt; Animal & gt ;, pub age: usize, }
Esses são apenas alguns tipos básicos que usaremos em nossos programas de exemplo para depuração.
O que é rust-gdb
?
rust-gdb
é um binário pré-construído que vem com a instalação do Rust (usando Rustup, por exemplo) e é instalado automaticamente.
Basicamente, rust-gdb
é um wrapper que carrega scripts externos de impressão bonita do Python no GDB. Isso é útil (e de certa forma necessário) ao depurar programas Rust mais complexos porque melhora significativamente a exibição de tipos de dados Rust.
Por exemplo, um Vec
se parece com isso com impressão bonita:
Vec (size=3)={rust_gdb_example:: Animal {kind: rust_gdb_example:: AnimalType:: Cat, name:"Chip", age: 4}, rust_gdb_example:: Animal {kind: rust_gdb_example:: AnimalType:: Gato, nome:"Nacho", idade: 6}, rust_gdb_example:: Animal {kind: rust_gdb_example:: AnimalType:: Cachorro, nome:"Taco", idade: 2}}
É assim sem:
alloc:: vec:: Vec & lt; rust_gdb_example:: Animal & gt; {buf: alloc:: raw_vec:: RawVec & lt; rust_gdb_example:: Animal, alloc:: alloc:: Global & gt; {ptr: core:: ptr:: unique:: Unique & lt; rust_gdb_example:: Animal & gt; {ponteiro: 0x5555555a1480, _marker: core:: marker:: PhantomData & lt; rust_gdb_example:: Animal & gt;}, cap: 3, alloc: alloc:: alloc:: Global}, len: 3}
Os scripts de impressão bonita fornecem formatação para as construções Rust mais amplamente usadas, como Vec
, Option
, Result
, etc., ocultando seus internos e mostrando os tipos reais de Rust-que é o que estaremos interessados na maior parte do tempo.
Essa também é uma das limitações claras das abordagens de depuração quando se trata do Rust no momento. Se você tiver tipos de dados complexos e aninhados, precisará de algum conhecimento de seus componentes internos ou de algum tipo de magia negra para inspecionar os valores adequadamente. Essa situação vai melhorar com o tempo, mas da forma como está atualmente, você terá problemas se depurar um software complexo do mundo real com essa abordagem.
Com a configuração resolvida, vamos começar com um programa de exemplo e iniciar o rust-gdb
com ele.
rust-gdb
exemplo
Vamos começar com um exemplo básico de como usar GDB com Rust.
Crie uma pasta examples
em seu projeto e adicione um arquivo basic.rs
com o seguinte conteúdo:
use rust_gdb_example:: *; fn main () { deixe animais: Vec & lt; Animal & gt;=vec! [ Animal { tipo: AnimalType:: Cat, nome:"Chip".to_string (), idade: 4, }, Animal { tipo: AnimalType:: Cat, nome:"Nacho".to_string (), idade: 6, }, Animal { tipo: AnimalType:: Dog, nome:"Taco".to_string (), idade: 2, }, ]; get_chip (& amp; animais); } fn get_chip (animais: & amp; Vec & lt; Animal & gt;) { deixe chip=animals.get (0); println! ("chip: {:?}", chip); }
Este programa muito simples inicializa uma lista de animais e chama uma função no final para imprimir o primeiro animal da lista.
Para depurar isso, precisamos compilá-lo e então executar rust-gdb
com o binário. Certifique-se de construir isso com o modo de depuração e NÃO no modo de lançamento.
construção de carga--exemplo básico Objetivo (s) de desenvolvimento [unoptimized + debuginfo] concluído em 0,28 s rust-gdb target/debug/examples/basic
Se não estivermos criando exemplos, mas um binário, o binário estará em target/debug
.
Ao executar o rust-gdb
, somos recebidos pelo GDB com algumas linhas de mensagem de boas-vindas e um prompt de entrada (gdb)
.
Se você nunca trabalhou com o GDB antes, esta folha de dicas do GDB pode ser útil.
Vamos definir um ponto de interrupção, que podemos fazer usando o comando break
ou simplesmente usando b
:
(gdb) b get_chip Ponto de interrupção 1 em 0x13e3c: exemplos de arquivo/basic.rs, linha 26. (gdb) informação b Num Tipo Disp Enb Endereço O quê 1 ponto de interrupção mantém y 0x0000000000013e3c em basic:: get_chip em examples/basic.rs: 26
Podemos definir pontos de interrupção nas linhas (por exemplo, basic.rs:17
) ou fornecendo uma função para quebrar. Podemos olhar os pontos de interrupção usando info b
, que nos mostra onde o ponto de interrupção está localizado, seu número (no caso de querermos deletar, desabilitar ou habilitar) e se está habilitado (
O comando info
pode ser usado com outros sinalizadores, como info locals
, que mostra variáveis locais, info args
, que exibe entrada argumentos de função e muitas outras opções.
Agora que configuramos nosso ponto de interrupção, podemos executar o programa executando run
ou simplesmente r
:
(gdb) r Iniciando programa:/home/zupzup/dev/oss/rust/rust-gdb-example/target/debug/examples/basic [Thread de depuração usando libthread_db ativado] Usando a biblioteca libthread_db do host"/lib/x86_64-linux-gnu/libthread_db.so.1". Ponto de interrupção 1, basic:: get_chip (animals=0x7fffffffd760) em examples/basic.rs: 26 26 deixe chip=animals.get (0);
Isso inicia o programa. Paramos no ponto de interrupção definido, na primeira linha da função get_chip
. Aqui, podemos ver os argumentos da função e tentar imprimi-los:
<(gdb) args de informação animais=0x7fffffffd760 (gdb) p animais $ 1=(* mut aloc:: vec:: Vec & lt; rust_gdb_example:: Animal & gt;) 0x7fffffffd760 (gdb) p * animais $ 2=Vec (tamanho=3)={rust_gdb_example:: Animal {kind: rust_gdb_example:: AnimalType:: Cat, name:"Chip", age: 4}, rust_gdb_example:: Animal {kind: rust_gdb_example:: AnimalType:: Cat , nome:"Nacho", idade: 6}, rust_gdb_example:: Animal {kind: rust_gdb_example:: AnimalType:: Cachorro, nome:"Taco", idade: 2}}
O comando info args
fornece uma visão geral dos argumentos recebidos. Quando imprimimos animais usando p
( print
também funciona), GDB nos diz que estamos lidando com um ponteiro para um Vec
, mas não nos mostra nenhuma informação relevante sobre o conteúdo do referido Vec
, pois é apenas um ponteiro.
Você também pode usar display
para imprimir uma variável, e há opções de formatação (como string, ponteiro, inteiro etc.) também. A diferença entre print
e display
é que, com display
, o valor é impresso novamente após cada instrução de passo. Isso é útil para monitorar mudanças em um valor.
Precisamos cancelar a referência do ponteiro usando * animais
. Se imprimirmos isso, teremos uma lista completa e legível de nossa lista de animais. Malabarismo básico de ponteiro e conversão de tipos são algumas das coisas que você precisa aqui e ali com referências a estruturas.
OK, onde estávamos? Vamos executar f
ou frame
para ver onde estamos:
(gdb) f # 0 basic:: get_chip (animals=0x7fffffffd760) em examples/basic.rs: 26 26 deixe chip=animals.get (0);
Certo, em nosso primeiro ponto de interrupção. Se ao menos houvesse uma maneira de ver graficamente onde estamos em nosso código-fonte...
Layouts e estado de inspeção
layouts
no GDB ajudam você a ver onde você está em seu código-fonte Rust. Usar o comando layout src
abre uma interface de linha de comando:
Nosso prompt de comando fica logo abaixo dele. Dessa forma, nunca ficaremos confusos sobre onde estamos. Existem também outros layouts, como layout split
, que mostra a fonte e a montagem correspondente:
Legal. Se você quiser se livrar do layout, pode usar CTRL + X a
. Se a renderização ficar complicada, CTRL + L
irá atualizá-la (isso acontece às vezes).
Assim como acontece com outros depuradores, podemos percorrer o código usando n
ou next
, ou entrar nas funções da linha em que estamos usando s
ou etapa
. Se quiser repetir isso, basta pressionar enter e o comando anterior será repetido.
Vamos dar um passo adiante e ver o que há dentro de nossa variável chip
após chamar .get
nos animais Vec
:
(gdb) n 28 println! ("Chip: {:?}", Chip); (gdb) chip p $ 3=core:: option:: Option & lt; & rust_gdb_example:: Animal & gt;:: Some (0x5555555a1480) (gdb) print * (0x5555555a1480 as & amp; rust_gdb_example:: Animal) $ 4=rust_gdb_example:: Animal {kind: rust_gdb_example:: AnimalType:: Cat, nome:"Chip", idade: 4}
Executamos n
e já estamos na próxima linha (28). Aqui, tentamos imprimir chip
e vemos que é uma Option
com uma referência a um Animal
dentro. Infelizmente, o GDB apenas nos mostra o endereço novamente; precisamos lançar o endereço para um & rust_gdb_example:: Animal
para ver os valores reais do animal.
Uma coisa boa é que muitas dessas coisas são preenchidas automaticamente. Portanto, se você começar a digitar rust_gd
, pressione TAB
para preenchê-lo automaticamente. O mesmo ocorre com o AnimalType
e outros tipos, funções e variáveis no escopo.
Também podemos imprimir definições de funções:
(gdb) p get_chip $ 11={fn (* mut aloc:: vec:: Vec & lt; rust_gdb_example:: Animal & gt;)} 0x555555569370 & lt; basic:: get_chip & gt;
Se quisermos chegar ao final desta função e um passo acima para o site de chamada, podemos usar terminar
. E se terminarmos com nosso ponto de interrupção atual, podemos usar continue
ou c
para continuar a execução do programa-que, neste caso, simplesmente executará o programa até o fim:
(gdb) terminar Execute até sair de # 0 basic:: get_chip (animals=0x7fffffffd760) em examples/basic.rs: 28 chip: Some (Animal {kind: Cat, name:"Chip", age: 4}) 0x0000555555567d87 em basic:: main () em examples/basic.rs: 22 22 get_chip (& amp; animais); (gdb) c Continuando. [Inferior 1 (processo 61203) saiu normalmente]
Muito bom. Estes são os fundamentos necessários para depurar programas Rust. Vejamos outro exemplo e exploremos algumas técnicas mais avançadas.
Manipulação de estado e pontos de controle
Primeiro, vamos criar outro exemplo dentro da pasta examples
dentro do arquivo nested.rs
:
use rust_gdb_example:: *; fn main () { deixe animais: Vec & lt; Animal & gt;=vec! [ Animal { tipo: AnimalType:: Cat, nome:"Chip".to_string (), idade: 4, }, Animal { tipo: AnimalType:: Cat, nome:"Nacho".to_string (), idade: 6, }, Animal { tipo: AnimalType:: Dog, nome:"Taco".to_string (), idade: 2, }, ]; deixe mut some_person=Person { nome:"Algum".to_string (), animais de estimacao, idade: 24, }; println! ("pessoa: {:?}", alguma_pessoa); alguma_pessoa. idade=100; some_person.name=some_func (& amp; some_person.name); } fn some_func (name: & amp; str)-& gt; Corda { name.chars (). rev (). collect () }
Novamente, estamos criando uma lista de animais. Mas desta vez, também criamos uma Person
e definimos os animais como seus bichinhos de estimação. Além disso, imprimimos a pessoa, definimos sua idade para 100
e revertemos seu nome (é isso que some_func faz).
Antes de podermos depurar este programa, precisamos construí-lo novamente e iniciar rust-gdb
com o binário:
construção de carga--exemplo aninhado rust-gdb target/debug/examples/aninhado
Ótimo. Vamos definir pontos de interrupção na linha 22 e linha 27 e executar o programa:
(gdb) b nested.rs:22 Ponto de interrupção 1 em 0x17abf: exemplos de arquivo/nested.rs, linha 22. (gdb) b nested.rs:27 Ponto de interrupção 2 em 0x17b13: exemplos de arquivo/nested.rs, linha 27. (gdb) informação b Num Tipo Disp Enb Endereço O quê 1 ponto de interrupção mantém y 0x0000000000017abf em nested:: main em examples/nested.rs: 22 2 ponto de interrupção mantém y 0x0000000000017b13 em nested:: main em examples/nested.rs: 27 (gdb) r Iniciando programa:/home/zupzup/dev/oss/rust/rust-gdb-example/target/debug/examples/aninhado [Thread de depuração usando libthread_db habilitado] Usando a biblioteca libthread_db do host"/lib/x86_64-linux-gnu/libthread_db.so.1". Ponto de interrupção 1, nested:: main () em examples/nested.rs: 22 22 let mut some_person=Person {
Estamos no primeiro ponto de interrupção, onde a pessoa é criada. Vamos continuar com a declaração de impressão. Em seguida, definiremos o chamado ponto de controle em some_person.age
. Este ponto de controle nos notificará sempre que some_person.age
mudar:
(gdb) c (gdb) watch some_person.age Ponto de controle 3 do hardware: some_person.age (gdb) n pessoa: Pessoa {nome:"Alguns", animais de estimação: [Animal {tipo: Gato, nome:"Chip", idade: 4}, Animal {tipo: Gato, nome:"Nacho", idade: 6}, Animal {tipo: Cachorro, nome:"Taco", idade: 2}], idade: 24} 28 alguma_pessoa. idade=100; (gdb) n Ponto de controle 3 do hardware: some_person.age Valor antigo=24 Novo valor=100 0x000055555556bba8 em nested:: main () em examples/nested.rs: 28 28 alguma_pessoa. idade=100;
O GDB nos mostra qual ponto de controle foi acionado, bem como o valor antigo e o novo.
Vamos executar o programa novamente chamando run
novamente e confirmando que queremos executar novamente. Desta vez, quando estivermos no segundo ponto de interrupção, vamos alterar o valor manualmente usando set
:
(gdb) set some_person.age=22 (gdb) p alguem_pessoa $ 1=rust_gdb_example:: Person {name:"Some", pets: Vec (size=3)={rust_gdb_example:: Animal {kind: rust_gdb_example:: AnimalType:: Cat, nome:"Chip", idade: 4}, rust_gdb_example:: Animal {kind: rust_gdb_example:: AnimalType:: Cat, nome:"Nacho", idade: 6}, rust_gdb_example:: Animal {kind: rust_gdb_example:: AnimalType:: Cachorro, nome:"Taco", idade: 2}}, idade: 22}
Como você pode ver, podemos usar set..args
para manipular o estado de nossas variáveis. Isso funciona muito bem com primitivos, mas fica mais complicado com valores complexos, como Biblioteca padrão Rust ou tipos de caixas externas. Esta é outra desvantagem, mas espero que melhore no futuro.
Outro recurso interessante que podemos tentar é executar funções e ver o que elas retornam:
(gdb) p some_func ("Olá") $ 3="olleH" (gdb) p some_func ("Depurar") $ 4="gubeD" (gdb) p some_func (some_person.name) $ 5="emoS" (gdb) set some_person.name=some_func (some_person.name) (gdb) p alguem_pessoa $ 6=rust_gdb_example:: Pessoa {nome:"emoS", animais de estimação: Vec (tamanho=3)={rust_gdb_example:: Animal {kind: rust_gdb_example:: AnimalType:: Gato, nome:"Chip", idade: 4}, rust_gdb_example:: Animal {kind: rust_gdb_example:: AnimalType:: Cat, nome:"Nacho", idade: 6}, rust_gdb_example:: Animal {kind: rust_gdb_example:: AnimalType:: Cachorro, nome:"Taco", idade: 2}}, idade: 22}
Podemos chamar a função some_func
, que está no escopo, com uma string literal. Também podemos chamá-lo com nosso some_person.name
e podemos usar set
para definir o nome da pessoa com o valor invertido.
Isso é muito poderoso e permite que você inspecione o resultado de expressões e funções no momento da depuração, o que pode ser útil para encontrar problemas. Isso, novamente, funcionará bem para casos simples, mas se você estiver, por exemplo, tentando executar uma função que faz I/O ou outras coisas mais complexas, você pode encontrar barreiras. Mas para 99 por cento dos casos, a funcionalidade existente aqui funciona bem.
Falando em I/O, vamos dar uma olhada em um exemplo final: como depurar um aplicativo de rede assíncrono em Rust usando GDB.
Depuração de um aplicativo de rede assíncrono
Por último, mas não menos importante, tentaremos depurar um aplicativo de rede assíncrona, em execução no tempo de execução assíncrono Tokio.
Vamos criar tokio.rs
na pasta exemplos
:
use std:: io; use tokio:: io:: AsyncWriteExt; use tokio:: net:: {TcpListener, TcpStream}; # [tokio:: main] assíncrono fn main ()-& gt; io:: Resultado & lt; () & gt; { let listener=TcpListener:: bind ("127.0.0.1:8080"). await ?; println! ("Aceitando TCP na porta 8080"); ciclo { let (socket, _)=listener.accept (). await ?; tokio:: spawn (movimento assíncrono {processo (soquete).await}); } } processo fn assíncrono (soquete mut: TcpStream) { tomada .write_all (b"Olá") .aguardam .expect ("pode escrever no soquete"); }
Este programa muito simples inicia um ouvinte TCP na porta local 8080
e, para cada conexão de entrada, chama de forma assíncrona a função process
, que lida com a solicitação.
A função process
simplesmente escreve Hello
, o que torna este o “aplicativo de rede” mais simples possível.
No entanto, complexidade não é o que procuramos aqui. Em vez disso, estamos tentando determinar se o fluxo de trabalho de uso de GDB muda quando depuramos programas assíncronos, como um servidor da web.
Vamos compilar o exemplo e iniciar rust-gdb
com o binário resultante:
construção de carga--exemplo tokio rust-gdb target/debug/examples/tokio
Até agora, tudo bem.
Vamos definir um ponto de interrupção no início da função processo
na linha 17:
(gdb) b tokio.rs:17 (gdb) informação b Num Tipo Disp Enb Endereço O quê 1 ponto de interrupção mantém y & lt; MULTIPLE & gt; 1.1 y 0x000000000009aa87 em tokio:: process:: {{closure}} em examples/tokio.rs: 17 1.2 y 0x00000000000a57fa em tokio:: processar em examples/tokio.rs: 17
Interessante, o ponto de interrupção é dividido em 1.1
e 1.2
. Esses são chamados de locais no GDB. Isso pode acontecer devido a otimizações, como inlining, por exemplo, onde o GDB adicionará um ponto de interrupção em cada ponto onde a função for inline ou modelada. Presumo que isso se deva à macro tokio:: main
, que envolve todo o código no tempo de execução do Tokio.
Podemos desativar qualquer um dos locais se quisermos, mas isso não importa neste caso. Vamos executar o programa:
(gdb) r Iniciando programa:/home/zupzup/dev/oss/rust/rust-gdb-example/target/debug/examples/tokio [Thread de depuração usando libthread_db ativado] Usando a biblioteca libthread_db do host"/lib/x86_64-linux-gnu/libthread_db.so.1". [Novo tópico 0x7ffff7c1e700 (LWP 55035)] [Novo tópico 0x7ffff7a1d700 (LWP 55036)] [Novo tópico 0x7ffff781c700 (LWP 55037)] [Novo tópico 0x7ffff761b700 (LWP 55038)] [Novo tópico 0x7ffff741a700 (LWP 55039)] [Novo Tópico 0x7ffff7219700 (LWP 55040)] [Novo Tópico 0x7ffff7018700 (LWP 55041)] [Novo tópico 0x7ffff6e17700 (LWP 55042)] Aceitando TCP na porta 8080
Nosso ouvinte está instalado e funcionando e podemos até ver os tópicos que o tempo de execução do Tokio gerou em segundo plano.
Vamos enviar alguns dados para o endpoint de outra sessão de terminal usando netcat
:
nc 127.0.0.1 8080
Isso aciona nosso ponto de interrupção em processo
:
[Mudando para Tópico 0x7ffff6e17700 (LWP 55041)] Thread 9"tokio-runtime-w"atingiu Breakpoint 1, tokio:: process:: {{closure}} () em examples/tokio.rs: 18 18 soquete (gdb) soquete p $ 4=tokio:: net:: tcp:: stream:: TcpStream {io: tokio:: io:: poll_evented:: PollEvented & lt; mio:: net:: tcp:: stream:: TcpStream & gt; {io: core:: option:: Option & lt; mio:: net:: tcp:: stream:: TcpStream & gt;:: Some (mio:: net:: tcp:: stream:: TcpStream {inner: mio:: io_source:: IoSource & lt; std:: net:: tcp:: TcpStream & gt; {estado: mio:: sys:: unix:: IoSourceState, inner: std:: net:: tcp:: TcpStream (std:: sys_common:: net:: TcpStream {inner: std:: sys:: unix:: net:: Socket (std:: sys:: unix:: fd:: FileDesc {fd: 11})}), selector_id: mio:: io_source:: SelectorId { id: core:: sync:: atomic:: AtomicUsize {v: core:: cell:: UnsafeCell & lt; usize & gt; {value: 1}}}}}), registration: tokio:: io:: driver:: registration:: Registro {handle: tokio:: io:: driver:: Handle {inner: alloc:: sync:: Weak & lt; tokio:: io:: driver:: Inner & gt; {ptr: core:: ptr:: non_null:: NonNull & lt; alloc:: sync:: ArcInner & lt; tokio:: io:: driver:: Inner & gt; & gt; {ponteiro: 0x55555573a560}}}, compartilhado: tokio:: util:: slab:: Ref & lt; tokio:: io:: driver:: ScheduledIo:: ScheduledIo & gt; {valor: 0x55555573ec20}}}} (gdb) c
Quando o ponto de interrupção é acionado, o GDB nos notifica que isso aconteceu em um dos threads gerados do tempo de execução e que temos a variável socket
, que podemos inspecionar.
O socket
é um Tokio TCPStream, mas não podemos dizer muito apenas imprimindo-o. Há um descritor de arquivo com o número 11
lá, que é a conexão de rede aberta, mas o resto parece ser Tokio e mio internals.
Em qualquer caso, funcionou-definimos com sucesso um ponto de interrupção em um manipulador assíncrono em execução em um dos vários threads. Isso significa que a mesma abordagem funcionará tão bem se tivermos, por exemplo, um servidor da Web Actix ou warp em execução, definindo um ponto de interrupção em uma das funções do manipulador, para inspecionar os dados de solicitação HTTP recebidos.
Aqui está a resposta Hello
em nosso segundo terminal depois de usarmos c
para continuar a execução:
nc 127.0.0.1 8080 Olá
Isso conclui nossa jornada para depurar aplicativos Rust usando GDB.
Você pode encontrar o exemplo de código completo no GitHub .
Conclusão
Neste tutorial de depuração do Rust, demonstramos como depurar um aplicativo Rust com GDB. Na maior parte, funciona muito bem, especialmente com as extensões de impressão bonita rust-gdb
, se tudo o que você está fazendo é percorrer um programa com pontos de interrupção e inspecionar o estado do programa.
Quando se trata de funcionalidades mais complexas, você pode estar acostumado com depuradores de GUI sofisticados em outras linguagens, esta é uma área de desenvolvimento ativo e espero que o ecossistema de depuração no Rust melhore. Quanto tempo isso levará e quão boa será a experiência geral de depuração, em comparação com os melhores depuradores da classe no mundo Java e C/C ++, é difícil dizer e dependerá da demanda por tais ferramentas no Rust.
O objetivo deste tutorial era fornecer as ferramentas para realizar a depuração básica de seus programas Rust com o mínimo de ferramentas extras ou conhecimento necessário. Este histórico deve cobrir a maioria dos casos que você encontrará, especialmente ao entrar no Rust.
A postagem Depurando aplicativos Rust com GDB apareceu primeiro em LogRocket Blog .