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?

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 ( Enb ).

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:

Layout GDB SRC 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:

Layout GDB Dividir Visual

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 .

Source link