Category Archives: Computação

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.04: Linker

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

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

Agora vamos falar um pouco sobre linker e porque precisamos nos preocupar com isso quando estamos codificando um sistema operacional.

Linker

Linker

Credito Wikipédia

Um linker é um programa que vai fazer a ligação entre os diversos módulos do programa. Esses módulos podem ser arquivos objeto gerado por códigos fonte, biblioteca externas ou qualquer coisa parecida. Ele vai transformar todos os símbolos do seu programa(como variáveis e funções) em endereços de memoria física, que pode ser tanto para um arquivo no computador quanto para a memoria.

Com toda a certeza, você deve saber que o computador não usa o nome da sua função que você declara em “C” para chamá-la/executá-la. Nem o nome das variáveis, que são acessadas através de endereços de memoria também. Isso também é uma afirmação valida para quando você programa em ASM, os rótulos que você usa não são usados diretamente. Quem traduz previamente esses nomes para endereços físicos e é responsável por ligar todos esses elementos é o programa conhecido como linker.

Para construir nosso SO, precisamos mudar um pouco o comportamento desse linker, através de um linker script(que posso encurtar para chamar apenas de linker, para facilitar).

Porque precisamos de um linker?

Precisamos que o kernel siga alguns padrões para que ele possa ser reconhecido e iniciado corretamente pelos gerenciadores de boot(tal como o LILO e GRUB). Para que isso aconteça, precisamos seguir o padrão “Multi Boot”, que vamos falar um pouco mais a frente o como vamos fazer isso.

Mais para o futuro, vamos precisar que construtores e destrutores sejam executados antes do inicio da função “main()” e depois que ela terminar. Para isso, através do linker, vamos precisar saber onde eles estão guardados na memoria.

Precisamos disso porque não existe maneira de garantir isso em ASM ou em C apenas, se existisse como, poderíamos usar o linker script padrão do sistema mesmo.

O que é o Multi Boot(mboot)?

Inicializar o sistema do computador é um trabalho complicado. Existem diversos hardwares com varias formas que ficaram ainda mais complexas com o passar dos anos por causa de modos de compatibilidade e outras coisas. Para que você não se preocupe com a inicialização do computador, foi criado os gerenciadores de boot. Para que o gerenciador de inicialização entenda que seu programa é um kernel iniciável de forma mais fácil, foi criado o padrão multi boot. Existem outras formas, mas vamos usar o mboot apenas.

Para que o gerenciador de boot saiba que seu kernel usa o padrão, e para que ele receba algumas informações necessárias, no começo do arquivo do kernel(ele procura nos primeiros 8K bytes) você deve colocar uma estrutura de dados que contem um numero magico(para que ele possa identificar o começo da estrutura entre os dados) e mais alguns dados. Tudo isso mais a frente vamos codificar diretamente em ASM, mas por enquanto, precisamos apenas garantir que o linker coloque a estrutura no começo.

O multi boot tem mais de uma versão, que se diferencia pelas informações que tem na estrutura binaria no começo do kernel. Como não vamos precisar das funcionalidades da segunda versão, vamos implementar apenas a primeira mesmo.

Para entender melhor em detalhes o formato binário do mboot, você pode consultar o manual da GNU.

Mas, por enquanto não vamos implementar nada além do linker script, só depois, então não se preocupe por estar um pouco abstrato demais.

Construindo nosso linker script

memory

Esquema da memoria do nosso kernel

O Nosso script vai ser algo parecido com isso:

ENTRY(start)

SECTIONS
{
	. = 1M ;
	.text : ALIGN(4096)
	{
		code = .; _code = .; __code = .;
		KEEP(*(.mboot*))
		*(.header*) ;
		*(.text*) ;

		*(.gnu.linkonce.t*) ;
	}
	code_end = .; _code_end = .; __code_end = .;

	.rodata : ALIGN(4096)
	{
		rodata = .; _rodata = .; __rodata = .;

		start_ctors = .;
		*(SORT(.ctors*))
		end_ctors = .;

		start_dtors = .;
		*(SORT(.dtors*))
		end_dtors = .;

		*(.rodata*)
		*(.gnu.linkonce.r*)
	}
	rodata_end = .; _rodata_end = .; __rodata_end = .;

	.data : ALIGN(4096)
	{
		data = .; _data = .; __data = .;

		*(.data)
		*(.gnu.linkonce.d*)
	}
	data_end = .; _data_end = .; __data_end = .;

	.bss : ALIGN(4096)
	{
		bss = .; _bss = .; __bss = .;
		sbss = .;
		*(COMMON)
		*(.bss)
		*(.gnu.linkonce.b*)
	}
	bss_end = .; _bss_end = .; __bss_end = .;

	end = .; _end = .; __end = .;
}

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

ENTRY(start)

Primeiramente, precisamos dizer que a função que vamos iniciar chamando é a “start“. Porque não “main()“, como o tradicional? Porque vamos precisar executar alguns códigos antes de chamar a função “main()”, que vamos descobrir melhor em passos futuros.

	. = 1M ;

Depois, vamos colocar os dados do kernel apenas depois da posição de memoria 0x00100000, mas porque? O gerenciador de boot usa a mesma memoria que o kernel, então essa memoria pode estar sendo usada para outras finalidades ou para passar dados para o kernel que acabou de iniciar. Além do que essa memoria pode estar sendo mapeada para outras coisas.

Essa posição inicial é relativa quando o programa estiver na memoria, seguindo os padrões de um arquivo executável que o gerenciador de boot vai preparar para executar. Ou seja, o arquivo real do kernel não vai ter 1M sem nada no começo(vai existir uns poucos dados do formato do binário, na verdade), apenas vai existir esse 1M de diferença quando ele estiver sendo executado na memoria.

	.text : ALIGN(4096)
	{
	....
	.rodata : ALIGN(4096)
	{
	....
	.data : ALIGN(4096)
	{
	....
	.bss : ALIGN(4096)
	{
	....

Vamos passar por todas essas secções criadas de uma vez. Elas são padrão em programas, porque basicamente precisamos das seguintes seções em um programa:

  • .text O código executável do programa fica nessa área de memoria
  • .rodata Como é de se esperar, as variáveis constantes e globais ficam nessa área de memoria
  • .data Área das variáveis globais que são inicializadas em tempo de compilação
  • .bss Variáveis globais não inicializadas ficam aqui

Outras seções, como common, sbss, gnu.linkonce.* e outras são apenas variações dessas áreas, mas são usadas pelo compilador e estão ai apenas para facilitar e prevenir erros no futuro.

Também vale a pena ressaltar que essas secções são alinhadas com paginas de memoria do tamanho de 4K, para ser mais fácil fazer paginação no futuro e ser mais otimizado.

		KEEP(*(.mboot*))

Lembra que precisamos garantir que a estrutura que identifica o kernel como mboot no começo do arquivo do kernel que vai ser gerado? É aqui que garantimos isso. Todas as seções que comecem com .mboot vão ser colocadas nesse local, no começo. O KEEP garante que ele vai ser colocado ali, independente se o linker trocar de lugar essa secção(por causa de otimizações ou por não ser referenciado em nenhum outro lugar).

		start_ctors = .;
		*(SORT(.ctors*))
		end_ctors = .;

		start_dtors = .;
		*(SORT(.dtors*))
		end_dtors = .;

Mais um pedaço de código que vamos usar mais a frente. Vamos ter a indicação de onde começa e termina os construtores e destrutores de objetos globais, justamente para executá-los no momento certo.

	end = .; _end = .; __end = .;

Futuramente, para saber qual memoria esta disponível para ser usada, vamos precisar saber onde a memoria usada pelo kernel termina. Essa marcação vai ajudar nisso. Usamos vários nomes para essa variável por comodidade.

	code_end = .; _code_end = .; __code_end = .;
....
	rodata_end = .; _rodata_end = .; __rodata_end = .;
....
	data_end = .; _data_end = .; __data_end = .;
....
	bss_end = .; _bss_end = .; __bss_end = .;

Adicionei essas marcações apenas para facilitar encontrar o final das secções. Você poderia até fazer sem elas, mas faz mais sentido fazer com as marcações. E fica mais intuitivo quando for implementar o multi boot no ASM.

Palavras finais

O que temos que focar aqui é a estrutura de como o kernel vai ser usado montado na memoria(e também no arquivo) e o que precisamos para ser compatível com o multi boot.

Aprender a linguagem de script do linker é algo que não é necessário, apenas precisamos saber por alto ela para que possamos garantir algumas coisas.

Algumas coisas aqui só vão ser utilizados mais para a frente, e a questão do boot nos próximos posts.

Caso alguém note algo que eu escrevi errado, algum conceito que esteja errado e/ou alguma dica, pode mandar mensagem para minha pessoa de forma amigável, tanto por comentário quanto por qualquer outra forma.

PS:. BUG FIX

Certo, enquanto produzia a parte a frente do post, notei que alguns bugs(o que é estranho, porque eu não erro :p) existiam no código antigo. Como já dei “push” e voltar agora seria muito complicado, caso notem alguma coisa diferente no repositório do que esta no post, pegue a ideia do que esta no post e use a versão correta. A versão correta pode ser ou do código que o post esta mostrando, ou a versão que vai aparecer.

De qualquer forma, eu já acertei esse post. Mas fica uma lista do que era:

  • Erro no CMake.
    • Faltou incluir um código para compilar o kernel em outra pasta sem ser a do projeto.
    • Pequenos ajustes para que o CMake funcionasse sem a opção de uma biblioteca C, que eu vou adicionar no futuro.
  • O Linker faltou algumas marcações

Encare essa nota de rodapé como um log de alterações do futuro. Não precisa se preocupar com essa secção, e se encontrar algum erro no continuo espaço tempo, use o do futuro :p.


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…


Instalando o flash no Chromium e no firefox no Ubuntu

O problema

Well, não gosto do ubuntu, mas recentemente eu me deparei com um problema em um computador com ubuntu.

Como todos devem saber, o suporte ao flash usando uma determinada API foi descontinuado(que é a NPAPI), e com isso o firefox e o chromium não tem mais suporte ao flash. O chrome não sofre com isso porque convenientemente ele vem com uma versão do flash embarcado no navegador, que usa uma outra API(que é a PPAPI).

A solução

Dito o problema, como resolver isso é muito simples, só usar o novo plugin do flash para a API nova.

Basicamente, para instalar o flash para o firefox e o chromium no ubuntu, é só executar:


sudo apt-get install pepperflashplugin-nonfree && sudo update-pepperflashplugin-nonfree --install
sudo add-apt-repository ppa:nilarimogard/webupd8 && sudo apt-get update && sudo apt-get install freshplayerplugin

Comentários sobre

A solução é para o ubuntu, mas pode facilmente adaptada para outras distribuições.

É uma bosta essas coisas, e quem sofre é usuario, mas fazer o que né. Só fica o desejo que esse povo deixe as coisas mais claras no futuro, principalmente para os usuários finais ai.


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.


O caso do lavabit, e porque não se deve confiar na liberdade nos EUA

http://lavabit.com/

Certo, vi isso hoje, mas acessem o link acima para entender toda a historia melhor, mas seque um resumo.

Existia uma companhia chamada lavabit que era dos EUA e tinha como proposta armazenar de forma segura dados de clientes.

O governo processou a companhia de uma forma que o dono ficasse totalmente sem defesa, deixando apenas duas alternativas: ou ele encerrava as operações ou ele entregava o acesso a todos os dados dos seus clientes, mesmo sem ser preciso ou encorajado pela lei.

Como ele era uma empresa seria, ele resolveu fechar as portas. E apesar de não poder fazer outra coisa e ter sua esperança na liberdade dos EUA abalado, ele ainda juntou paciência dele e fez o relato acima do link. Muito interessante de acompanhar.

Continue reading


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


O mais simples, funcional e disponivel servidor de arquivos HTTP

Precisava de um servidor HTTP para servir arquivos em uma pasta entre computadores.

Obviamente, eu já fiz um servidor em C++ para isso, mas esse servidor é meu, eu tinha que baixar, compilar e fazer muitas coisas que podiam não estar disponíveis no momento.

Procurando uma solução para esse problema, eu encontrei algumas soluções boas, mas, a melhor foi baseada em python:

python2 -m SimpleHTTPServer

Ou:

python3 -m http.server

Lembre de rodar esse comando dentro da pasta que você quer servir.


Apresentação SIASE

Apresentei um mini-curso no SIASE(não sei o link do site…), e minha apresentação foi essa aqui.

Estou postando porque disse que ia colocar o link da apresentação no meu blog, e para falar um pouco sobre a minha impressão.

Bom, o evento pecou um pouco em organização, mas as palestras foram interessantes e as ideias boas. Realmente esse tipo de evento é o que precisa ser feito periodicamente.

Com relação a cidade, Santana(que coincidentemente é a cidade onde meu pai nasceu), tive uma boa impressão. Como em 2 lugares excelentes, um chamado capim verde e o outro Xonkantes, algo assim, que realmente foram muito bons.

A hospedagem que ficamos foi muito boa também, ótima vista, bem localizada, e com um bom preço. O quarto tinha ar-condicionado também, que devido ao calor da cidade era bem necessário…

Também fui na casa de um parente de uma amiga, e tenho a dizer que o povo de lá é legal.

Ou seja, foi uma boa viagem, um bom mini-curso, pena que não consegui descansar o tempo necessário lá, porque não fui a passeio também né? 😉


DIY: Como construir o seu próprio arcade parte 01

A algum tempo, eu pensei em fazer um arcade, só que meio que desisti porque não tenho tempo e espaço para um arcade.

Mas, aparentemente alguns amigos meus estão se reunindo para essa empreitada, o que torna bem mais fácil de fazer, e legal também!

Como eu sou um quase freak de planejamentos, traçar um plano para um projeto é sempre um bom começo na minha mente, nem que para que no final ele seja todo desfeito…. Durante a minha pesquisas, eu consegui montar um padrão do que fazer para montar um arcade funcional em casa emulando jogos ou uma replica ou um arcade original.

Porque não faço um trull arcade Jamma from Hell of seven kings replica? Simples, eles são mais chatos de fazer, caros, e além de tudo, mais difícil de manter, mas pouca coisa muda, a não ser a dificuldade de conseguir e montar as coisas, então serve também essas dicas que eu vou dar.

Então vamos as minhas considerações, espero que seja útil, e qualquer coisa que eu esqueci me lembrem ai para que o meu arcade também saia descente.

O tipo

Existem vários tipos de console, e aqui decidimos o tipo que vamos fazer. Essa é a primeira e fundamental parte do projeto, porque facilita ou não algumas etapas, apesar de todo mundo normalmente escolher um arcade tradicional.

Quanto aos tipo, temos:

  • Tradicional: Visual clássico, normal de fazer, difícil de transportar e mais difícil de alocar em casa.
  • Mini/Nano arcade: Visual parecido com o clássico, um pouco mais fácil de fazer, normal para transportar e um pouco mais fácil de guardar.
  • Cocktail: Tem um pouco visual clássico, mais difícil de fazer, difícil de transportar e mais fácil de alocar em casa, fazendo um sucesso em festas também ;).
  • Portable arcade: tem zero de visual clássico, mas para jogadores a fim do jogo, extremamente fácil de fazer, extremamente fácil de transportar e fácil de guardar.

O nano e o mini são iguais, a única questão mesmo é o tamanho e que no caso do nano é mais difícil ter mais jogadores do que um no mesmo arcade…

Poderia explicar o que é cada um, mas imagens são melhores:

Arcade tradicional

Arcade tradicional

Mini arcade

Mini arcade

arcade cocktail

arcade cocktail

portable arcade

portable arcade

Espaço

Essa é a parte mais fácil de se pensar.

Existem 2 momentos a se pensar durante todo o processo, os 2 muito importantes mas não interferem no resto.

Primeiro, onde vai ser montado.

Precisa de um espaço bem maior do que final, além de material e onde trabalhar para fazer o seu arcade. Se você optar por construir tudo do zero, esse espaço ter que lidar com madeira, porque cedo ou tarde você vai ter que fazer alguma alteração na estrutura da carcaça do arcade.

Segundo, o lugar onde vai ficar.

Pode parecer simples, mas tem que ser lembrado que o arcade precisa conseguir entrar no local… Sem contar em sair também, inclusive de onde ele estará sendo montado.

Inclusive, pensando em facilitar o transporte, coloque alças, entradas na carcaça para melhor forma de levantar, ou coisas assim, se alguém pensar em mais algo diz ae.

Fim

E basicamente isso ai que eu lembro e pesquisei. Não existem muitas dicas aqui, é mais a parte que você tem que pensar no básico mesmo.

Próximo post vou falar sobre a carcaça, algumas dicas uteis que eu pesquisei, o que é legar ter, e algumas considerações que eu não coloquei em pratica. Acho legal começar pela carcaça para que o visual do arcade fique bonito, por isso é bom cuidar dessa parte externa antes.

Lembrando que eu ainda não fiz nada, qualquer duvida ou dica eu gostaria de comentassem ai!


%d bloggers like this: