Tag Archives: c++

Sistema Operacional miniOS-0.05: Código ASM de inicialização

Continuando a serie de posts sobre como fazer um sistema operacional. O código desta parte também já esta no repositório.

Nesse post vamos finalmente ter alguma ação, vamos codificar algo. E mais interessante ainda, em ASM.

O que ser assembly?

asm

A linguagem Assembly(vamos chamar simplesmente de ASM) é uma linguagem de baixo nível, que quer dizer que tem uma abstração quase inexistente da linguagem de maquina.

Devido a sua pouca abstração, o ASM normalmente é extremamente não portável. Isso quer dizer que, o ASM é extremamente atrelado a ISA e ao SO para a qual esta se programando.

Existem diversas variações de ASM, e cada um tem suas características. Uns podem ser mais portáveis, ter uma linguagem de mais alto nível e/ou outras funcionalidades.

O paradigma de programação em ASM normalmente é bem linear e não estruturado. E a forma mais básica da linguagem é através de mnemônicos de instruções, se repetindo a cada linha do programa, imitando a linguagem de maquina.

Como ASM é altamente ligado a ISA, normalmente vamos precisar de uma implementação diferente para cada ISA. De vez em quando podemos contornar isso, mas no nosso kernel por enquanto estamos implementando para i386 apenas, então não vamos nos preocupar tanto com isso.

Conhecer totalmente ASM não é obrigatório, mas entender um pouco do que vai ser codificado mais a frente é necessário. Não vamos codificar muito em ASM, apenas o que realmente for preciso.

Porque precisamos do ASM?

Infelizmente, não é possível escrever totalmente o kernel em C/C++. Mas porque, você se pergunta?

C/C++ não fornece funcionalidades para garantir que o cabeçalho do mboot fique no começo do kernel. Se você declara dados em C/C++, esses dados ficam na secção “.data”, “.rodata” e/ou outras(como foi abordado em posts passados). Teoricamente, você pode escrever em ASM diretamente em C/C++, mas isso é conversa para o futuro.

Quando você executa uma função em C/C++, você teoricamente já esta usando a pilha, então não da para indicar uma função de C/C++ como ponto de inicio do kernel sem usar uma pilha. Não temos como garantir com C/C++ que essa pilha previamente configurada esteja usando uma área de memoria valida. Isso pode fazer com que o código sobrescreva uma área de memoria importante ou até acesse uma área de memoria mapeada para alguma outra finalidade, podendo causar erros.

Não podemos garantir que funções em C/C++ sejam iniciadas sem alterar registradores específicos. Então se indicarmos uma função de C/C++ como ponto de inicio do kernel, quando ela for iniciada vamos perder informações de alguns registradores. Isso não tem nenhuma forma de contornar(que eu saiba, acho que rótulos do C/C++ não podem garantir isso…), e isso vai se tornar mais evidente quando estiver programando o IDT, GDT e etc

Se existir formas de fazer isso apenas em C/C++ padrão/portável(ou o mais possível…), por favor me avisem :p.

Implementando nossa rotina de inicialização em ASM

Vamos criar o arquivo /minios/kernel/arch/i386/pc/start.s. Nessa pasta porque é dependente da ISA que estamos usando(i386 no caso, só para separar).

Nesse arquivo temos algo parecido com:

.section .mboot

/* Multi Boot header */
.set MBOOT_PAGE_ALIGN, 1<<0
.set MBOOT_MEM_INFO, 1&lt;<span id="mce_SELREST_start" style="overflow:hidden;line-height:0;">&#65279;</span>&lt;1
.set MBOOT_HEADER_MAGIC, 0x1BADB002
.set MBOOT_HEADER_FLAGS, MBOOT_PAGE_ALIGN | MBOOT_MEM_INFO
.set MBOOT_HEADER_CHECKSUM, -(MBOOT_HEADER_MAGIC + MBOOT_HEADER_FLAGS)

.globl mboot
.globl start

.align 4
mboot:
	.long	MBOOT_HEADER_MAGIC
	.long	MBOOT_HEADER_FLAGS
	.long	MBOOT_HEADER_CHECKSUM

	.long	mboot
	.long	code
	.long	data_end
	.long	bss_end
	.long	start

.section .text

initial_stack:
	.globl	initial_stack
	.zero	4

multiboot_magic:
	.globl	multiboot_magic
	.zero	4

multiboot_addr:
	.globl multiboot_addr
	.zero	4

start:
	cli

	movl %eax, multiboot_magic
	movl %ebx, multiboot_addr
	mov $stack_top, %esp
	movl %esp, initial_stack

.hang:
	hlt
	jmp  .hang

/* Create stack on bss section */
.section .bss

.align 16
stack_bottom:
	.skip 16384 # 16 KiB
stack_top:

Vamos agora a uma explicação mais detalhada. Vamos focar em pontos importantes mais a frente.

.section .mboot

Na primeira linha, com o section garantimos que essa secção vai ficar em .mboot, que reservamos para o começo do kernel no linker, como explicado no post anterior.

.set MBOOT_PAGE_ALIGN, 1<<0
.set MBOOT_MEM_INFO, 1<<1
.set MBOOT_HEADER_MAGIC, 0x1BADB002
.set MBOOT_HEADER_FLAGS, MBOOT_PAGE_ALIGN | MBOOT_MEM_INFO
.set MBOOT_HEADER_CHECKSUM, -(MBOOT_HEADER_MAGIC + MBOOT_HEADER_FLAGS)

Algumas definições do Multi Boot. Basicamente o numero magico que identifica o cabeçalho e algumas informações que vamos passar para o gerenciador de boot.

Vamos utilizar apenas opções básica, então isso já resolve. Para entender melhor o valor de cada campo e outras opções, vocês podem consultar a pagina da GNU com todas as informações.

.align 4
mboot:
	.long	MBOOT_HEADER_MAGIC
	.long	MBOOT_HEADER_FLAGS
	.long	MBOOT_HEADER_CHECKSUM

	.long	mboot
	.long	code
	.long	data_end
	.long	bss_end
	.long	start

Esse é o cabeçalho binário do Multi Boot propriamente dito, que devemos manter no começo do kernel para que o gerenciador de boot consiga as informações necessárias e identifique o kernel como inicializável.

Perceba que é necessário que esses bytes sejam alinhados em 4, como especificado pelo padrão.

Os últimos 5 inteiros são exatamente os endereços de memoria que marcamos no linker: O endereço do cabeçalho, o endereço onde se inicia o código que vamos copiar para a memoria, o endereço até onde vamos carregar para a memoria, o endereço final da área BSS(essa área vai ser preenchida com zeros pelo gerenciador de boot, de acordo com o padrão) e o endereço de onde o gerenciador de boot vai começar a executar o kernel!

Esses últimos 5 inteiros são importante, mas é bom ressaltar que não vamos usar e eles estão ali apenas por compatibilidade. O formato do binário do kernel que vamos usar é ELF(que o mboot já sabe interpretar o cabeçalho com essas informações), então todas essas informações já estão no kernel(além de contar com outras vantagens, como um kernel menor). Se você não colocar no cabeçalho especificamente para usar essas informações(colocando 1 no bit 16 em MBOOT_HEADER_FLAGS), ele já vai carregar elas do cabeçalho do padrão ELF. Você deve estar se perguntando porque essas informações vão ficar ai? Bom, caso no futuro eu mude de formato, fica ai a opção(acho que alguns sistemas podem não ter suporte a ELF…). Também não estou certo se todos os compiladores podem gerar executáveis no formato ELF.

.section .text

initial_stack:
	.globl	initial_stack
	.zero	4

multiboot_magic:
	.globl	multiboot_magic
	.zero	4

multiboot_addr:
	.globl multiboot_addr
	.zero	4

Apenas definindo algumas variáveis globais em ASM e preenchendo com zeros. Essas variáveis não precisam ser definidas aqui, poderiam ser definidas em C/C++, acho. Provavelmente vamos remover elas quando os códigos em C/C++ começarem a aparecer.

Vamos falar da função de cada uma dessas variáveis quando atribuirmos os valores a elas mais a frente.

start:
	cli

FINALMENTE!!11!1

Aqui é o rotulo/endereço de onde o seu kernel vai começar a executar depois que o gerenciador de boot fizer todo o trabalho duro. Esse rotulo vai ser tipo um “main()” do ASM. Apesar de ser difícil programar em ASM, você poderia começar a fazer sua baguncinha por aqui…..

Mas em outro dia, porque primeiramente temos que desabilitar as interrupções com a instrução cli. Porque executamos essa instrução logo? Porque, o computador esta com uma tabela de interrupções que foi configurada pelo gerenciador de boot e outras, que podem fazer comportamentos inesperados(na verdade, acho que o gerenciador de boot já desabilita, mas no futuro vamos fazer isso de novo quando for desativar o kernel).

No futuro, podemos mover essa instrução para uma função em C/C++ para ficar mais organizado, mas por enquanto estamos usando apenas ASM.

	movl %eax, multiboot_magic
	movl %ebx, multiboot_addr
	mov $stack_top, %esp
	movl %esp, initial_stack

Aqui, fazemos apenas algumas atribuições, mas elas são importantes. Muito tem a ver com o porque precisamos usar ASM no lugar de C/C++ na inicialização, como discutimos antes.

Primeiro, precisamos do conteúdo do registrador EAX, que copiamos para a área de memoria/variável chamada multiboot_magic. Porque? O gerenciador de boot vai colocar nesse registrador um numero magico(2badb002), que identifica que o kernel deu boot corretamente por um gerenciador compatível com mboot versão 1.

Segundo, precisamos do conteúdo do registrador EBX, que copiamos para a área de memoria/variável chamada multiboot_addr. Porque? Ele contem o endereço do cabeçalho do mboot, para que o kernel possa extrair informações extras que o gerenciador de boot possa passar. Na verdade não vamos usar isso, mas não custa nada armazenar.

E, para finalizar, deixamos de usar a pilha que estamos usando atualmente(que é de uma área de memoria que o gerenciador de boot usou) para utilizar uma área de memoria segura indicada pelo rotulo stack_top. Para quem não sabe, o registrador ESP indica onde esta a pilha, caso você não entenda como isso funciona, apenas assuma que isso é necessário. É necessário usar uma área de memoria valida e que possa crescer, por isso vamos alocar essa memoria na secção BSS.

No caso, essa são as únicas operações que precisamos fazer em ASM por agora. Se colocássemos uma função em C/C++ no lugar do código em ASM, o C/C++ iria usar essa pilha que o gerenciador de boot indicou anteriormente e adicionalmente provavelmente iriamos perder os conteúdos dos registradores EAX e EBX, porque o código de inicialização da função de C/C++ usaria esses registradores.

.hang:
	hlt
	jmp  .hang

Aqui é o final do programa. Futuramente, vamos passar essa parte do código para C/C++ também.

Porque precisamos desse código? Basicamente, hlt para e espera pela próxima interrupção que o computador vai receber. Se por algum motivo alguma interrupção acontecer(não era para acontecer, mas pode), vamos executar hlt de novo, até que o computador pare de vez.

Basicamente, um loop infinito executando a instrução para parar.

.section .bss
.align 16
stack_bottom:
	.skip 16384 # 16 KiB
stack_top:

Dentro da secção BSS, reservamos 16384(vulgo, 16KiB) de memoria alinhada a 16 e marcamos o começo e o final dessa área para ser a nossa pilha.

A secção BSS é a área de memoria que definimos para essas finalidades. Mas, porque precisamos alinhar? Porque o x86 diz que precisamos(tá, tem motivos para isso, mas como você é obrigado não tem opção).

Lembra que alguns passos atrás falamos sobre a pilha? Lá usamos essa área de memoria que reservamos aqui, simples assim.

Não precisamos nos preocupar com a ordem, apenas precisamos colocar os dados nas secções corretas. Isso é valido para a pilha ficar na BSS, quanto qualquer outro código que usamos mais a cima ou depois.

Executando

Agora podemos executar o código depois de compilado usando o qemu. Precisamos de dois requerimentos para isso:

  1. O emulador de i386 ou parecido
  2. Um GCC que produza executáveis para i386

Você pode fazer um toolchain para a arquitetura que você vai usar, que envolve produzir todo um ambiente de compilação para a arquitetura especifica. Mas, i386 é muito comum, então pode ser que você tenha o compilador já no seu sistema. Enfim, não vou entrar em detalhes disso, porque não vai ser meu foco.

Considerando que tudo esta certo, você poderia compilar e executar o SO assim:

git clone -b 'V00.05' https://bitbucket.org/psycho_mantys/minios
mkdir minios/build
cd minios/build
cmake ..
make
../tools/boot_qemu.sh

Pronto! Claro, esses comandos são para usar um ambiente linux/unix e usando linha de comando. No seu ambiente, você pode utilizar qualquer outra ferramenta gráfica ou procedimento que achar mais interessante para baixar, compilar e rodar o sistema operacional.

Analisando a execução

No meu ambiente(e espero que seja parecido em outros também), depois de executar o kernel usando o qemu, vamos ter uma imagem mais ou menos assim:

run_qemu

Certo, o que mudou você se pergunta? Exatamente, nada :/.

Apesar de todo o trabalho que fizemos até agora, praticamente o nosso kernel não faz nada(nenhuma alteração visual na tela, ainda vamos chegar lá). Poderíamos executar algum código no rotulo “start” além do que já colocamos lá, mas teríamos que programar em ASM, que não é nosso escopo por agora.

Como vamos saber que estamos fazendo tudo certo então? Basicamente, de duas formas.

A primeira é olhando a saída do comando “/tools/boot_qemu.sh” que estamos usando para executar o kernel. Nesse script codificamos para que ele informe o conteúdo dos registradores depois de um tempo, que é quando o kernel já deve estar parado no loop infinito. Como nosso kernel esta em conformidade com o padrão mboot versão 1, então, no registrador EAX o gerenciador de boot deve colocar o valor magico(um magic number) “2badb002” para garantir que o kernel foi iniciado por um gerenciador de boot compatível(você poderia atribuir algum valor magico a esse registrador também e verificado depois).

Analisando a saída, podemos ver que isso é verdade:

run_qemu_cli_editado

Um problema que pode acontecer é do seu kernel não ter ou não estar com o cabeçalho do mboot certo. Se você estiver implementando da forma correta no “start.s”(na secção correta e os dados certo), você também pode verificar no arquivo binário do kernel se o cabeçalho esta certo e no lugar correto. Como podemos fazer isso? Você pode abrir o arquivo binário(usando um visualizador de binário com o comando hexdump ou até qualquer editor de sua escolha) e verificar se os dados estão no começo. Também tem como verificar isso usando o grub-file. Para uma forma mais conveniente, eu vou usar o comando “objdump” e verificar se a secção esta em um offset do arquivo perto do começo:

run_objdump

Como podemos ver, a secção esta localizada pelo inicio do arquivo, o que é bom.

Recapitulando o processo todo

Agora que temos algo executando, creio que seja importante recapitular o processo todo de inicialização passo a passo de forma direta:

  1. O gerenciador de boot procura no arquivo binário do kernel o cabeçalho do mboot
  2. Gerenciador de boot carrega para a memoria o conteúdo do kernel indicado no endereço no quinto campo do cabeçalho até o endereço do final(que é indicado no sexto campo) ou usa o valores no cabeçalho ELF
  3. Gerenciador de boot preenche com zeros a memoria da BSS até o endereço de memoria indicado no sétimo campo do cabeçalho ou o indicado no cabeçalho ELF
  4. Gerenciador de boot coloca o numero magico no registrador EAX para indicar que o kernel foi inicializado pelo padrão do mboot versão 1
  5. Com todo o ambiente configurado, o gerenciador de boot passa o fluxo de execução para o endereço que é indicado no oitavo campo cabeçalho ou o indicado no cabeçalho ELF
  6. Executamos o código do kernel

Uma boa leitura sobre o multiboot versão 1 é o manual da GNU. Apesar de não explicar direito o processo de compilação e muita coisa ser implementada em ASM e sem explicação, é uma consulta bem explicativa no mínimo para o processo de boot.

Palavras finais

O foco dessa parte não é ensinar ASM, mas é um conhecimento interessante de se ter, pelo menos o básico.

Em vários pontos em fiquei tentado a implementar o mboot versão 2. Apesar de serem parecidos, não tem muito necessidade dentro do que estou fazendo. Fica mais fácil fazer com o padrão 1 porque já sei o que fazer mesmo. Talvez no futuro eu reveja isso.

Algumas coisas eu estou relembrando, e uma parte do processo esta sendo refazer algumas coisas. Caso algo seja estranho ou pareça errado, seria bom que alguém me avise.

Uma boa coisa é se alguém souber melhores formas de fazer alguma coisa, é bom me avisar pelos comentários, ou dar sugestões para melhorar. Não existe uma melhor forma única de fazer tudo, mas estamos tentando utilizar de todas as melhores pelo menos.

Não sei se a didática esta boa, sinto que estou sendo um pouco prolixo de vez em quando, mas acho que é bom em alguns momentos. E como estou implementando enquanto vou postando, não sei ainda a melhor maneira.

Enfim, qualquer ideia falem ai.

Advertisements

Sistema Operacional miniOS-0.03: Build automatizado

Estou continuando a serie de posts sobre como fazer um sistema operacional, fazendo o meu próprio sistema. (Quase como se não tivesse passado muito tempo….)

Nesse post, espero falar um pouco mais sobre o gerenciamento do build, ou seja, muita coisa sobre o CMake. Não espere que esse seja um tutorial sobre CMake, então não vou entrar em conceitos básicos do gerenciados de build. Também vou adiantar conceitos que não vão ser usados agora.

Planejo falar um pouco sobre como vamos usar o compilador mais para o final.

Quando essa parte do tutorial estiver publicada no meu blog, você já poderão conferir no repositório os fontes dos arquivos.

Porque?

Originalmente, as primeiras impressões que eu tive com o build de um sistema operacional foi usando nada(apenas o compilador na forma pura) ou no máximo com o uso do Makefile.

O Makefile é muito cru e sinceramente não fornece capacidades tão boas para ser portável, para realizar tarefas mais complexas ou mais simples de forma fácil. Mas, apesar disso, alguns materiais mais antigos só tinham o Makefile a disposição, e por isso ele era amplamente usado.

Quando pensei em melhorar o processo de desenvolvimento do SO, automaticamente pensei em usar um gerenciador de build mais pratico. Como eu queria melhorar minha pericia com o CMake, vamos usar ele mesmo.

O que seria Build?

Quando falo em build(sou mais acostumado a usar essa palavra, não me passa na cabeça uma melhor em português agora), eu na verdade estou me referenciando ao processo de construir o programa.

Construir o seu programa pode ter varias etapas, como por exemplo transformar arquivos fonte em arquivos que vão ser usados em produção, gerar informações que seu programa vai precisar, configurar o programa final que você vai gerar, e por ai vai. Tudo isso com finalidade de facilitar processos repetitivos.

No caso do nosso sistema operacional, temos que escolher as opções que vamos usar com o compilador atual, escolher que biblioteca C vamos usar e qual a arquitetura que vamos gerar o kernel. Claro, além de compilar todo o kernel.

Quem é acostumado com o kernel do linux, vai lembrar do seu tradicional “make menuconfig”. Seria mais ou menos essa parte do processo.

Como fazemos?

No arquivo /CMakeLists.txt do código fonte podemos escolher essas opções através de variáveis. Para alterar essas variáveis eu recomendo usar o comando:

ccmake .

Se você passar por esse arquivo, notara que ele incluí diversos outros arquivos e usa algumas funções. Vamos mais a frente falar sobre esses arquivos e sobre o que essas funções fazem.

/minios/kernel/arch/CMakeLists.txt

O primeiro arquivos importante. Não vou passar linha por linha dele aqui, mas você pode previamente ver ele no link acima.

Basicamente, temos uma única função aqui, LOAD_ISA_PROFILE, que aceita dois argumentos, a ISA e a plataforma. Como existem códigos que podem variar dependendo desses dois parâmetros, essa função serve para passar os arquivos e configurações desses parâmetros. Caso não exista um arquivo nas pastas que sobrescrevam o padrão(veja como isso é feito no código fonte), essa função passa a construir o kernel com todos os arquivos *.c, *.cpp e *.s que se encontram dentro das pastas padrão para essa plataforma e a ISA.

Certo, mas o que é essa tal de ISA? Instruction set architecture(ISA) é o modelo abstrato de um computador. Como vamos ter programar em ASM em alguns pontos, esse tipo de código normalmente varia de acordo com a plataforma e/ou a ISA que você esta usando. Por isso, se existisse mais de uma implementação do OS para outras ISAs, aqui que separaríamos o código que vamos utilizar.

Porque separamos a ISA da plataforma? Sinceramente, não sei se isso é tão importante. Creio que seria importante caso fosse feito otimizações ou algo assim, por isso separei.

/minios/kernel/compiler/CMakeLists.txt

No link acima, podemos ver o arquivos que define a função LOAD_COMPILER_PROFILE, que aceita como parâmetro o compilador que vamos usar para compilar o nosso kernel.

Essa é uma parte interessante, porque precisamos disso? Porque, todo compilador, apesar de que você não saiba disso(ou talvez saiba, ou imagine isso), insere código a mais nos seus programas. Que códigos são esses? Códigos para que seu programa funcione em determinados sistemas operacionais, para que você utilize bibliotecas que o sistema fornece e até mesmo inserindo funções extras que não dependem de bibliotecas externas.

Porque saber disso é importante? Porque, no sistema operacional você não tem como contar com essas comodidades, então você precisa desabilitar. O que significa isso? Quer dizer que, a partir de agora, todas as bibliotecas que você usar devem ser suas e não ligadas dinamicamente, além do que você não tem uma inicialização do seu programa, e mais para a frente nos vamos fazer essa inicialização, que chamamos de boot do sistema operacional.

Como vamos utilizar o GCC, para que a função do cmake que eu fiz funcione, temos que criar o arquivo:

/minios/kernel/compiler/gcc/flags.cmake

Dentro desse arquivo, vamos dizer para o gcc usar as sequintes flags para compilar nosso kernel:

set(COMPILER_C_FLAGS "-Wall -Wno-main -nostdlib -nostdinc -fno-builtin -nostartfiles -nodefaultlibs -fno-exceptions -fno-stack-protector -ffreestanding")
set(COMPILER_CXX_FLAGS "-ffreestanding -O2 -Wall -Wextra -nostdlib -nostdinc -fno-builtin -fno-stack-protector -Wno-main -nostartfiles -fno-exceptions -fno-rtti")

Uma lista grande de flags, que basicamente quer dizer: Não precisa construir a função “main()” do programa, deixa que vamos resolver. Não utilize bibliotecas do sistema e nem a biblioteca básica. Não use funções internas do GCC e retire as funções incompatíveis de C++.

Sobre C++: Inicialmente, construtores e destrutores não vão estar 100% disponíveis, além de RTTI e exceções. Construtores e destrutores vamos dar um jeito no futuro(eu rezo para isso…), mas o resto das coisas é mais complicado, porque requer que implementamos partes da biblioteca padrão do C++, isso da tanto trabalho que é melhor programar sem…

Palavras finais

Vejam o código, porque aqui é apenas uma passada rápida. Sinto que eu deveria documentar mais algumas coisas, mais por enquanto esses posts ficam como fonte de documentação.

Qualquer pessoa pode usar o que quiser nesse ponto, ou até usar o GCC puro sem gerenciador de build, mas como eu já fiz isso e sei o que vai precisar, eu coloquei essa etapa antes. Sendo bem sincero, essa estrutura foi pensada mais no processo final quando eu programei o SO da primeira vez.

Como importante, o que se tem que tirar daqui é o fato de não poder contar com nada que o compilador e SO nativo oferecem para produzir um kernel. Nada de bibliotecas e/ou inicialização. Se quiserem saber mais também, o manual do GCC é uma boa para procurar sobre essas flags de compilação.

Se notarem algo de estranho, me falem. Possivelmente posso ir corrigindo esses posts com o tempo também…


Sistema Operacional miniOS-0.02: Configuração do Ambiente

Ola terráqueos. Voltei.

Estou continuando a serie de posts sobre como fazer um sistema operacional, fazendo o meu proprio sistema.

Agora vou tratar da configuração do ambiente, ou em outras palavras, deixar as ferramentas que não são de desenvolvimento ajustadas para facilitar. Basicamente vamos falar do diretório /tools do nosso projeto.

Quando essa parte do tutorial estiver publicada no meu blog, você já poderão conferir no repositório os fontes dos arquivos.


/tools/boot_qemu.sh

Primeiramente, vamos falar sobre esse arquivo, que vai ser o script que vai ajudar a inicializar o nosso kernel.

Para testar o kernel, eu optei por usar o qemu, que é um emulador de maquinas bem completo e com suporte a virtualização, ou seja, podemos também usar o kernel um uma maquina virtualizada com pouco esforço.

Não vou explicar o script, acho que isso não é necessário, mas a função básica é verificar se o qemu de 32 bits esta disponível no sistema, se não estiver, usar o comando padrão. Depois, se a variável de ambiente estiver sem nenhum valor, ele procurar a imagem do kernel com o nome padrão(“miniOS”) e inicializa o sistema. Como vamos precisar por questões de debug saber o conteúdo dos registradores, esse script vai esperar um tempo para que a maquina levante o sistema e vai mostrar o conteúdo dos registradores.

O objetivo vai se rodar esse comando depois de construir o “executável” do sistema, dentro do diretório central do código fonte.

Originalmente, no tutorial do Bran e em outros, se usa um “emulador” antigo mas que funcionava bem legal, que era o bochs. O bochs é funcional e foi muito usado durante muito tempo, mas ele sempre foi mais difícil de usar, além de ter alguns “bugs” que não me pareciam muito fáceis de lidar… Por isso resolvi usar uma ferramenta mais fácil e moderna, por isso o qemu ;).

Para quem quer usar uma interface, eu recomendo o aqemu. Ele tem a opção de colocar um kernel para dar boot, e fica mais pratico. Não vou abordar isso, tanto porque acho o script mais fácil ^^.

Como sempre, o código agora já esta no repositório.


LibreSSL e porque o openssl ainda é perigoso

Certo, em épocas de heartbleed e coisas do tipo, começamos a perceber que existem alguns problemas com algumas implementações não tão modernas

Mais especificamente com o OpenSSL, recentemente na tentativa de evitar outros erros futuros, pessoas começaram a olhar e relatar problemas adicionais com o openSSL. Devido a vários problemas, foi resolvido criar um fork do projeto chamado “libreSSL“.

Realmente não gosto muito da ideia de se fazer um fork de forma desnecessária, mas procurando sobre o assunto, encontrei esses slides que justificam o clone:

http://www.openbsd.org/papers/bsdcan14-libressl/mgp00001.html

Recentemente soltei um twett sobre um post falando sobre a baixa qualidade em códigos científicos. Acho que esse é um caso de grande parte das coisas que são motivos do clone até.

Ah, também acho interessante que o projeto é vinculado ao openBSD, mas ainda assim ele é portável. Quer dizer, acho que a maior parte das coisas feitas no openBSD são portáveis mesmo.

UPDATE:

Um link muito útil que o Dielson colocou para entender o Heartbleed:

http://www.dwheeler.com/essays/heartbleed.html


Sistema Operacional miniOS-0.01: Estrutura de diretórios

Voltei! Dessa vez, para começar e por enquanto, um post mais fácil, pelo menos. Vamos falar sobre a estrutura de diretórios do projeto de um sistema operacional, e o que temos que ter e onde, além de algumas dificuldades que já começou a aparecer.

Certo, vamos mostrar a estrutura de diretórios no geral, e depois eu vou explicar o porque de cada pasta é importante e para que serve, além de levantar umas ideias gerais sobre o futuro do que vai conter em cada uma das pastas.


/src/
/src/compiler/CC
/src/kernel/boot/
/src/kernel/kpp/
/src/kernel/fs/
/src/kernel/arch/
/src/kernel/arch/ISA/
/src/kernel/arch/ISA/PLATFORM
/src/libc/
/tools/

/tools

Esse diretório é o que temos um carinho especial. Aqui vamos colocar as ferramentas que vão nos ajudar a desenvolver nosso sistema operacional, vão facilitar o desenvolvimento, na verdade.

Também vamos colocar alguns programas não relacionados com o kernel em si, pelo menos porque vão ser códigos tão pequenos que não precisam criar repositórios a parte para eles, sendo mas simples deixar ai mesmo.

/src

Aqui vai ficar os códigos relacionados ao kernel, diretamente ao que vai entrar no kernel. Provavelmente, tudo o que vai ser compilado e construido dentro do kernel vai ficar em alguma pasta por aqui.

Na verdade, os códigos não vão ficar aqui, mas em pastas separadas pelas suas funções, como explicado abaixo.

/src/compiler/

Aqui começamos a entrar em detalhes interessantes. Vamos começar com um pequeno conto, o de como um programa normal funciona.

Quando você inicia um programa, você já começa com a sua função main(), certo? Mas quem iniciou as suas variáveis globais? E quem configurou algumas funções? Quem criou os arquivos stdin, stdout e stderr?

Você pode argumentar que tudo isso é função das variáveis globais(por exemplo, stdin, stdout e por ai vai…), mas, isso ainda tem funções especificas que você tem que saber sobre o ambiente para funcionar. Esse tipo de função é de códigos específicos do compilador! No futuro, esses códigos que vamos precisar, vão estar nesse diretório. A medida que precisarmos, eu vou explicando o porque, para que e como vamos precisar disso, então relaxe por enquanto e aceite que precisamos.

Uma coisa a mais a se considerar é que existem vários compiladores, então para cada compilador eu vou precisar de algum código especifico. Essa parte não tem jeito, muita coisa pode ser reusada, mas infelizmente normalmente os codigos são diferentes um dos outros. Para manter separado, vamos criar um diretório também para cada compilador, como por exemplo:


/src/compiler/gcc

Para cada compilador, um diretório. O como vamos usar assim, mais para frente a gente fala(próximo post?).

/src/kernel/

Vamos colocar aqui os códigos especificamente feitos para o kernel. Aqui vai ficar a lógica do kernel e coisas que tem a ver com arquitetura.

/src/kernel/boot/

Precisamos de alguns códigos para inicializar o kernel. Aqui é que ficam esses códigos, aqui é a cola entre o gerenciador de boot e o seu kernel, digamos que a interface, a passagem.

Não confundir com o gerenciadores de boot, que inicializa e carrega o kernel. Isso não é trabalho de um kernel, e não vamos fazer um, então deixa essa parte quieto.

/src/kernel/kpp/

Não podemos contar com nada, ou com muito pouco pelo menos, no desenvolvimento de um kernel. Isso quer dizer que não podemos usar as bibliotecas padrões que normalmente usamos, e isso quer dizer que vamos precisar reimplementar algumas coisas.

Para todas essas coisas que iremos reimplementar de algoritmos básicos(como vector, lista, etc…), vamos colocar nesse diretório.

Sobre o nome do diretório, é as iniciais de kernel plus plus, meio sem sentido mais com sentido…

/src/kernel/fs/

Nada de novo aqui, apenas vai ser o diretório com códigos relacionados ao sistema de arquivo.

Mais no futuro a gente resolve o que vai colocar aqui, principalmente porque não sabemos ainda como vai ser a cara de como lidar com sistema de arquivos.

/src/kernel/arch/

Com certeza vamos precisar utilizar algum código que seja especifico para alguma plataforma, e nesse diretório é que vamos colocar esses códigos que vão depender de alguma arquitetura.

O objetivo é que todos os códigos que dependam de alguma plataforma fiquem aqui, basicamente como eu quero isolar o código em ASM também, gostaria que todos os códigos ficasse aqui.

Mas não é sempre que podemos isolar o código ASM do código em C/C++. Existem alguns códigos de inicialização e alguns outros códigos que precisam ser escritos em ASM para serem mais portáveis ou funcionarem como você quer.

/src/kernel/arch/ISA

Dentro dos códigos mais específicos e em ASM da arquitetura, ainda temos códigos que dependem das informações especificas da ISA da maquina. Essas declarações e códigos vão ficar aqui.

Aqui vai ficar uma parte um pouco chata do trabalho já que também vamos lidar possivelmente com C, que é construir alguns tipos da ISA. pode ser que usemos algumas soluções mais com cara de C++, mas vai ser difícil…

Também temos que ter uma forma de escolher a ISA em que nosso sistema estará compilando. Isso vai fazer parte do sistema de build.

Uma coisa importante a se ressaltar é que o nome “ISA” no nome da pasta vai ser substituído pelo nome da ISA que você esta implementando, porque você pode ter varias implementações. Por exemplo, se você estiver implementando a ISA da arquitetura x86, a pasta seria:


/src/kernel/arch/x86

Isso tudo porque depois vamos usar isso para identificar e configurar a plataforma para onde vamos gerar o nosso kernel.

/src/kernel/arch/ISA/PLATFORM

Aqui ficaram códigos ainda mais específicos das plataformas, como na ISA, só que menos genéricos ainda e mais dependentes do hardware.

É meio difícil de explicar, e fica meio nebulosa a fronteira entre os códigos da ISA e os da plataforma, mas acho que é legal criar essa divisão para aproveitar códigos usados entre plataformas. Talvez seja trabalho demais para pouca coisa também, mas nesse momento não estou ligando para isso.

Assim como no caso da ISA, se estivermos por exemplo implementado a plataforma i386 da ISA x86, faríamos:


/src/kernel/arch/x86/i386

/src/libc/

Uma das piores coisas de se contruir um sistema operacional é que você não tem acesso a praticamente nenhuma ferramente pronta. Isso quer dizer que você mal pode contar com o seu compilador, imagine as bibliotecas basicas de C ou C++.

Para facilitar esse trabalho, de reimplementar todas as bibliotecas, você pode portar algumas bibliotecas. Como a biblioteca basica de C é muito importante, é aqui que teremos os ports de algumas bibliotecas para o nosso sistema.

Porque uma pasta só para isso? Porque o ideal é que possamos trocar a implementação de forma mais fácil, então aqui dentro terremos varias opções que poderão ser escolhidas pelo sistema de build do nosso kernel.

Fim!

Bom, basicamente seria isso ai, essa vai ser a estrutura de diretórios que já adianta alguma coisa para saber o que vamos fazer.

Entrei em vários pontos aqui, mas não falei como eles serão resolvidos, algumas partes porque eu realmente vou explicar melhor isso no futuro, outras porque ainda quero descobrir a melhor forma de resolver.

Mas o mais importante é, se alguem ler isso aqui e alguma duvida surgir, perguntem aqui nos comentários, que eu vou aprimorando cada vez mais o post e também vou tentando responder as duvidas. Tentem não ser tão ansiosos e se concentrar no post de agora!


C++: Parametrizar programa pela linha de comando, arquivo de configurações e/ou variáveis de ambiente

O problema

Olá amiguinhos! Recentemente, tive curiosidade de como fazer uma coisa um pouco mais complexa do que eu já vinha usando.

Todo mundo usa parâmetros para configurar o seu programa e não precisar recompilar ele toda vez para uma alteração nos dados e não na logica. Além de poder tonar o programa mais customizável, temos a vantagem de que desenvolver programas assim também é mais fácil e rápido(por motivos óbvios, use e confirme).

Até ai, eu estava fazendo e vivendo muito bem com isso usando a espetacular biblioteca de opções de programa, isso mesmo, a boost.program_options! Com ela, de forma fácil eu consigo fazer essa trabalho de parametrização do programa.

Só que, para quem já usou programas bem feitos, sabe que essa parametrização é pode ser feita de mais jeitos, e tradicionalmente, as 4 formas mais usadas: Por parâmetros de linha de comando, arquivo de configurações, variáveis de ambiente e/ou combinações dos 3 primeiros.

Eu queria fazer melhor, usar a forma mais completa, que é justamente a 4 forma. Eu ainda não sabia direito como fazer, mas sabia que a boost.program_options dava suporte a todas essas formas e talvez a mais algumas… Recentemente sentei, agrupei tudo o que eu já sabia, pesquisei algumas coisas, fiz uns testes e finalmente consegui chegar a um resultado razoável, que é o que eu lhes apresento agora ;).

A solução

Considere um programa chamado “sort“.

O programa precisa receber um arquivo de entrada e um de saída, sendo que valores padrões(a entrada padrão e a saída padrão) podem ser assumidos para esses dois caso eles não sejam fornecidos, e eles só devem ser configurados pela linha de comando.

O programa deve receber um numero de pivô, esse numero deve assumir um valor padrão caso não se diga o contrario por algum parâmetro.

O programa também deve exibir uma mensagem de ajuda, mas somente se for requisitado via linha de comando. Essa mensagem de ajuda serve para saber como entrar com todos os parâmetros, via variáveis de ambiente, linha de comando e/ou arquivo de configuração.

Algumas opções devem ser alteradas por variáveis de ambiente começando com “SORT_“. Essas variáveis são as que não são as dadas pela linha de comando exclusivamente.

Finalmente, o código que resolve todos esses requisitos é:

Além do que eu falei, o código tem algumas coisinhas a mais, mas o principal é que cumpre os requisitos.

Fácil de entender, fácil de fazer. Na verdade, eu tive mais dificuldades e demorei mais em escrever os requisitos do que em escrever o código :p.


Sistema Operacional miniOS-0.00: Começando a fazer um do zero, o vai ser preciso?

Construindo um Sistema Operacional

Olá amiguinhos! Recentemente, eu comecei e terminei de construir um sistema operacional. OK, eu me orgulho disso, achei ele muito bem feito, mas ainda tem muitos pontos que eu vi que eu podia melhorar.

Então, agora, eu começo uma serie de posts em que eu pretendo refazer esse sistema, dessa vez muito mais organizado, comentando aqui no blog com posts explicando cada passo, comentando o código de forma realmente legal e inserindo alguns elementos a mais também, tudo isso com a opinião de vocês meus leitores.

Eu pretendo evoluir as ideias que eu já usei e/ou usar algumas ideias melhores que eu tinha pensado na época, mas como eu tinha tempo a cumprir eu não fiz. Além que também eu vou tentar refatorar muito do código que eu já tinha usado.

O plano de release

Como eu já fiz uma vez e tenho alguma noção, eu sei mais ou menos o caminho em ordem do que fazer, e em cada post eu vou tentar abordar cada um desses tópicos. Pelo menos esse é o plano…

Lista do que vou desenvolver em provável ordem:

  1. Estrutura de diretórios
  2. Configuração do Ambiente
  3. Build automatizado
  4. Linker
  5. Código ASM de inicialização
  6. Código “C” de inicialização
  7. Monitor
  8. kprintf
  9. Teste?
  10. Construtores globais
  11. Destrutores globais
  12. Biblioteca padrão “C
  13. Gerencia de memoria no final do kernel
  14. Algumas estruturas de dados
  15. Global Descriptor Table (GDT)
  16. Interrupt Descriptor Table (IDT)
  17. Interrupt Service Routines (ISR)
  18. Interrupt ReQuest (IRQ)
  19. Programmable Interval Timer (PIT)
  20. Teclado
  21. Interface para sistema de arquivo genérica
  22. Gerencia de memoria com paginação
  23. Multiprocesso
  24. Tratamento de Exceções?

Lembrando que provavelmente eu vou alterar esse lista com o tempo, porque eu posso mudar de ideia com relação a ordem ou posso quebrar em mais de um tema esses tópicos, coisas assim.

A parte que eu inicialmente acho mais difícil

Testes, esse é a parte realmente nova e que pode levar tempo. Requer que eu crie mais coisas também.

Testes influenciam o desenvolvimento depois, porque a arquitetura de testes influencia muito no desenvolvimento.

A complexidade é grande, e eu vou abordar melhor o porque no post sobre testes, em que eu devo resolver e propor alguma coisa.

O começo, algo feito

Como eu sempre gosto de deixar algo sempre pronto, com bônus inicial eu já criei o repositório para quem quiser ir acompanhando, no github.

Na verdade, é o repositório antigo que eu tinha feito, mas eu deletei(criei um branch, na verdade…) e recomecei do zero. Mas quem quiser já pode ir acompanhando, mas acho melhor que vocês falem comigo por aqui e vejam os posts aqui também ;).


C/C++: Accessors(getters) e mutators(setters) de forma fácil usando macros

Continuando

Anteriormente, acho que até o ultimo post que eu fiz, falei sobre accessors(getters) e mutators(setters).

Isso foi a algum tempo atrás, e apesar de ser uma maneira muito elegante, ainda é uma maneira um pouco repetitiva, no sentido que você deve se preocupar e repetir algumas partes do processo. Isso é uma parte do processo que pode ser feita de forma automática, com muitas vantagens, com uma ajudinha de macros.

Estava vendo que o Murilo fez algo de forma parecida, e achei muito interessante. Resolvi copiar e alterar para o modo como eu mostrei em meu post passado, e mudei algumas coisas, como o nome de “declare” para “declare_am”, só para fica mais explicito….

Vejam agora que toda vez que eu quiser declarar uma variável data do tipo inteiro com getters e setters, é só usar declare_am(int,data)! Muito fácil.

Segue o código abaixo, usem para facilitar a vida, e qualquer discussão estamos ai :].

#include	<iostream>
#include	<cstdlib>

#define declare_am(type, name) \
private: type _##name; \
public: \
void name(const type& var){\
	_##name = var;\
}\
\
const type & name(){\
	return _##name; \
}\

class Teste_accessor_mutator{
	declare_am(int,data);
	declare_am(int,x);
	declare_am(int,y);
};

int main( int argc, char *argv[] ){
	Teste_accessor_mutator teste;

	teste.data(100);
	teste.x(1);
	teste.y(2);
	std::cout << teste.data() << std::endl ;
	std::cout << teste.x() << std::endl ;
	std::cout << teste.y() << std::endl ;
	return EXIT_SUCCESS;
} /* ---------- end of function main ---------- */


C/C++: Accessors(getters) e mutators(setters) de um jeito diferente

Para que serve mutators e accessors?

Quando temos algum atributo em um objeto, esse atributo pode ser acessado e modificado para descobrir o estado do objeto.

Só que com o tempo, pode se fazer necessário que você tenha um maior controle no acesso a estados do objeto, para ter ou caso você queira um encapsulamento maior(Cache? Controle de acesso? Se que sabe 😉 ).

Com relação aos dados, existem duas operações possíveis para o acesso a um atributo do objeto, você pode acessar ou mudar o atributo. Com isso, você pode inserir uma camada nessas duas operações para poder ter esse encapsulamento a mais.

Já que sabemos o que fazer, então temos uma decisão aqui para tomar: Como iremos fazer isso? Existem algumas formas de se fazer isso, mas existe uma que utiliza polimorfismo em C++ que é mais particularmente interessante.

Ah, para quem ainda não entendeu ainda, os getters e setters servem como um estilo de implementar os accessor e mutators.

Como fazer em C++?

Cenário, dado que temos uma classe chamada Teste_accessor_mutator, que tem um atributo chamado _data. Esse atributo tem que ter um accessor chamado data e um mutator chamado data. Vamos ver isso em código implementado e um exemplo de acesso e mudança de atributo:

#include	<iostream>
#include	<cstdlib>

class Teste_accessor_mutator{
	private:
		int _data;
	public:
		const int &data(){
		return _data;
	}
	void data( const int &data ){
		_data=data;
	}
};

int main( int argc, char *argv[] ){
	Teste_accessor_mutator teste;

	teste.data(100);
	std::cout << teste.data() << std::endl ;
	return EXIT_SUCCESS;
} /* ---------- end of function main ---------- */

Pronto, com isso temos um getter e setter implementado! Perai, calma lá.

Porque eu usei _data como nome do atributo? Porque apesar de de C++ suportar polimorfismo, ele suporta polimorfismo entre métodos e não atributos. Como não vai ser usado o atributo a não ser internamente em raras ocasiões apenas colocar underline(_) na frente do atributo para mudar o nome não causa uma perda de visibilidade muito grande.

Além dessa alternativa, poderia ser qualquer outro nome no dado, como  por exemplo o atributo se chamar m_data. Mas esse estilo tem uma visibilidade boa e não difere do original, então é bem aceitável.

Porque não usar um get_data e um set_data? Não que seja um estilo ruim, mas os nomes dos métodos ficam um pouco poluídos com essa abordagem. Se você tem a vantagem do polimorfismo, fica mais legível o código.


Versão mais nova do gcc para windows

Boas novas

Quem usa a versão oficial para windows do gcc, sabe que a versão oficial suportada pelo windows do gcc e um pouco antiga…. Mais precisamente a versão 3.4. Tá, admito, é muito antiga…. E essa versão não suporta as melhores qualidades do gcc mais novo.
Bom, para aqueles que gostam de novidades e muito poucos riscos, estou avisando aqui que agora vocês pode ter o melhor compilador do mundo em seu computador com o sistema Windows.

Onde baixar

Através desse site http://www.tdragon.net/recentgcc/ você pode baixar a versão mais nova do gcc para o windows!

Que fique claro: Eu só testei via emulação(wine), portanto não vi os detalhes do compilador, só do instalador e de como desinstalar. Mais, segundo o que promete o site que você pode baixar o compilador, ele tem suporte a todas as funcionalidades legais do gcc mais novo. Entre elas:

  • Suporte a arquitetura core2.
  • Suporte a openmp.
  • Suporte a pthreads.

Só esses 3 itens já são suficientes para convencer a qualquer pessoa a pelo menos testar.

Recomendo também a leitura do site, lá ele pode explicar qualquer duvida que venha a existir. Inclusive sobre alguns bugs corrigidos e informações sobre o compilador.

Minhas Impressões

Apesar de não ter usado o compilador, eu baixei e instalei ele só para ter uma noção de como era o instalador  e sua praticidade.

Bom, posso dizer que fiquei impressionado ;). O instalador é melhor do que o do MinGW (o instalador que instala o gcc-3.4 no windows) e ainda é compatível com esse(O instalador é compatível).

Através do instalador você pode remover, alterar e instalar a versão mais nova do gcc no seu computador. Ainda existe a possibilidade de continuar um instalação passada, aproveitando os downloads dos pacotes do site que você já baixou.

Vale a pena mencionar que na instalação ele baixa os pacotes e instala na sua maquina. Ou seja, você tem que estar com acesso a intenet ou fingir uma continuação de instalação se você já tiver os pacotes baixados. O que falta eu acho que é uma versão alternativa completa, com os pacotes já no instalador para não precisar baixar do site os arquivos. Mas talvez já tenha, porque eu não procurei muito, então pode ser que exista e eu não sei ;).

Os detalhes

Vamos aos detalhes do projeto. Esse projeto tem duas versões do compilador, claro que na maioria da vezes você só ira usar uma, mais é sempre bom ter a outra opção. Essas opções são classificadas de acordo de como o binário ira resolver as excessões. Vou mostrar as duas opções abaixo, mais na maior parte das vezes a opção padrão e a melhor e você não deve se preocupar com essa parte.

Calma. Uma pausa antes de prosseguirmos. Se prepare, a partir de agora o nível vai descer. A leitura a partir de agora eu admito que é um pouco mais pesada, mas não se assuste!

Então vamos falar agora sobre como resolver pilhas! Let’s Go Talk About Stacks ;).

SJLJ

A primeira, que é a versão padrão do projeto, é a versão compilada SJLJ.

Desse trecho em diante é especulação, porque não sei direito o porque de usar SJLJ, mas posso especular de forma muito próxima da realidade.

A vantagem de usar a versão padrão com SJLJ é que em vez de usar outro metodo de resolução de exceções, o desenvolvedor desta versão do gcc usa as instruções setjmp(man setjmp) e a longjmp(man longjmp).

Sacou o porque do SJLJ? ;).

Usar essas funções em um binário é mais rápido para o binário a não ser que você use muitas excessões. Se você usar muitas excessões você ira ter um overhead nos try{…}. Isso é a excessão em códigos de computação científica e em geral, por isso, use a versão padrão SJLJ.

Detalhe, não há os try{..} que eu mencionei acima. Para quem programa em c++ é mais fácil dizer assim para que você possa entender, mais imagine que esses blocos são códigos em C que estão ai no começo e no final de uma porção de código para simular os blocos try{…}.

Ah sim, também é mais portável desenvolver algo em SJLJ, porque as funções setjmp e longjmp são funções da biblioteca padrão. E desconfio que seja mais fácil escrever código com SJLJ, apesar de nunca ter escrito com essas funções, eu consigo imaginar como implementar com elas as excessões, consigo ate imagina uma troca de contexto de hardware. Deve ser resultado da minha “forma de pensar em pilhas”.

DWARF-2

A segunda versão do compilador é a a versão não padrão compilada com DWARF-2(dw-2).

Também não sei muito sobre dw-2. Para falar a verdade, sei mais de SJLJ e quase nada de dw-2, e logo vou explicar o porque. Então como acima, o trecho a seguir é mais especulação ainda ;).

Vamos a vantagens do dw-2. Primeiro, não a overhead para as excessões. Ponto. Como ele faz isso? Ai entra a parte estranha do processo e o porque eu não sei quase nada do processo.

Primeiro devemos lembrar de uma vantagem do SJLJ: “portabilidade”. Porque isso é importante? Porque no dw-2 não tem nenhuma. A cada plataforma existe uma forma do compilador fazer o binário resolver a excessão de forma diferente sem overhead para cada maquina. Isso é muito complexo de se implementar pelo que eu posso ver. Envolve sinal em algumas plataformas, pilhas e frames em outras e qualquer outro método em outras plataformas. Isso é o que quer dizer não portável. Pode ser de qualquer forma que resolva o problema em outra plataforma ;).

Além disso, o código fica muito complexo e maior. Há formas de diminuir essa complexidade, mas ela ainda existe.

Fim

Bom, é isso. Baixem o gcc-4,3 para windows! Se eu usasse windows não perderia tempo ;).

Acho que eu acabei me alongando mais na parte de resolução de pilha no binário gerado, mas acho que foi empolgação. Fazia tempos que eu queria escrever algo assim, então acho que falar só do gcc para windows que eu nem uso seria um pouco down. E sempre desejei dar uma esclarecida sobre esse assunto. Bom, fica ai para a geração futura ;).

PS1:. Meu corretor ortográfico esta com defeito no site. Perdão erros ortográficos. ;).

PS2:. Esse post pode ter opiniões pessoais e tendenciosas. Se encontrar alguma, ignore e veja a mensagem geral do texto.


%d bloggers like this: