Você está na página 1de 125

CI064 - Software Bsico

Bruno Mller Junior


12 de Maro de 2012

Prefcio
Como professor do departamento de informtica da UFPR, ministrei vrias disciplinas na rea de sistemas, como Construo de Compiladores, Sistemas Operacionais,
Arquitetura de Computadores e Circuitos Lgicos.
Uma caracterstica comum a estas disciplinas a dificuldade de expor todo o contedo no nmero de horas destinado a cada uma.
Observei que alguns tpicos destas disciplinas exigem conhecimentos prvios dos
alunos, conhecimentos que no so apresentados formalmente. Exemplos incluem chamadas ao sistema1 , chamadas a procedimentos, uso da heap, uso de registradores e memria, execuo dos programas. Alm disto, programas de transformao de formatos
de arquivos, com montadores, ligadores, compiladores e carregadores, assim como os
seus formatos, tambm no so abordados.
Por esta razo, o professor de cada disciplina se v obrigado a ministrar estes conceitos, utilizando um tempo precioso do curso, e apresentando somente uma viso
daqueles conceitos de acordo com o enfoque daquela disciplina.
Como resultado, aqueles conceitos so fragmentados em partes que nem sempre
so consistentes nas cabeas dos alunos.
Para ensinar estes conceitos de uma forma mais consistente, o departamento de informtica da UFPR criou a disciplina Software Bsico. Esta disciplina foi ministrada
por mim por vrios anos, e este livro a compilao das minhas notas de aula.
A disciplina aborda a camada de software mais prxima ao hardware, e explica
conceitos que so implementados parcialmente no hardware e como isto usado em
sofware.
Para tal, usa-se a linguagem assembly para desenvolver aplicativos. Esta linguagem
no padronizada: cada CPU tem o seu conjunto de instrues, e uma linguagem
particular teria de ser escolhida. A nossa opo foi o assembly dos processadores da
famlia x86 (para 32 bits), uma vez que a maior parte do parque computacional do
departamento de informtica da UFPR composto por computadores com esta CPU.
Para uma viso mais completa, o livro tambm explica o que so programas executveis, interpretados, emuladores, alm de biblitecas estticas, compartilhadas e dinmicas.
Desde que esta disciplina foi implantada, o tempo dispendido nas demais disciplinas de sistemas foi diminudo (o que no quer dizer que sobra tempo), e seu nvel de
aprendizado dos alunos mostrou-se mais consistente.
1 system

calls

Organizao do Livro
O livro est organizado em duas partes:
Primeira Parte: Linguagem assembly da famlia x86. A linguagem assembly utilizada para exemplificar alguns dos conceitos de linguagens de programao,
de sistemas operacionais e de arquitetura de computadores como, por exemplo,
uso de registradores, memria, interrupes, chamadas ao sistema e de procedimento. As ferramentas (software) que utilizei so: so:
as (montador assembly do gnu)
ald ( assembly language debugger)
Uma opo ao ald o gdb (gnu debugger)
Segunda Parte: software de base (compiladores, ligadores, montadores e carregadores), os formatos intermedirios dos arquivos que eles geram (arquivos objeto,
arquivos executveis, arquivos fonte).

Contedo
I Traduo e Execuo de Programas
1 A Seo de Cdigo e de Dados
1.1 Esqueleto de programas em assembly
1.2 Expresses Aritmticas . . . . . . . .
1.3 Comandos Repetitivos . . . . . . . .
1.4 Traduo da construo While . . . .
1.5 Comandos Condicionais . . . . . . .
1.6 Vetores . . . . . . . . . . . . . . . .
1.7 Exerccios . . . . . . . . . . . . . . .

9
.
.
.
.
.
.
.

.
.
.
.
.
.
.

15
16
19
22
25
27
30
30

2 A Seo da Pilha
2.1 Modelo de Chamadas de Procedimento . . . . . . . . . . . . . . .
2.2 Implementao do Modelo em Uma Arquitetura . . . . . . . . . . .
2.2.1 Sem Parmetros e sem Variveis Locais . . . . . . . . . . .
2.2.2 Sem Parmetros e com Variveis Locais . . . . . . . . . . .
2.2.3 Com Parmetros e com Variveis Locais . . . . . . . . . . .
2.2.4 Parmetros passados por Referncia e com Variveis Locais
2.2.5 A funo main da linguagem C . . . . . . . . . . . . . . .
2.2.6 Chamadas Recursivas . . . . . . . . . . . . . . . . . . . .
2.2.7 Uso de Bibliotecas . . . . . . . . . . . . . . . . . . . . . .
2.3 Aspetos de Segurana . . . . . . . . . . . . . . . . . . . . . . . . .

.
.
.
.
.
.
.
.
.
.

33
33
34
35
37
39
39
41
44
48
48

3 Chamadas de Sistema
3.1 A analogia do parquinho . . . . . . . . . . . . . .
3.2 Acrescentando a CPU . . . . . . . . . . . . . . . .
3.3 Acresentando perifricos e dispositivos de hardware
3.4 Interrupes . . . . . . . . . . . . . . . . . . . . .
3.4.1 Interrupo de Hardware . . . . . . . . . .
3.4.1.1 Registrando o driver . . . . . . .
3.4.1.2 Disparando o driver correto . . .
3.4.1.3 Exemplo . . . . . . . . . . . . .
3.4.2 Interrupo de Sofware . . . . . . . . . . .
3.5 Os drivers nativos . . . . . . . . . . . . . . . . . .
3.6 Questes de segurana . . . . . . . . . . . . . . .

.
.
.
.
.
.
.
.
.
.
.

51
51
52
53
54
54
54
55
56
56
57
57

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.

CONTEDO

6
3.7

As chamadas ao sistema no Linux . . . . . . . . . . . . . . . . . . .


3.7.1 Conjunto de servios . . . . . . . . . . . . . . . . . . . . . .

59
59

4 A Seo BSS
4.1 Heap . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4.2 Exerccios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

61
65
66

II Software Bsicos

67

5 Software Bsicos

71

6 Formatos de Programa
6.1 Linguagem de alto nvel . . . . . . . . . . . . . . . . . . . . . . . .
6.2 Linguagem assembly . . . . . . . . . . . . . . . . . . . . . . . . . .
6.3 Linguagem de Mquina . . . . . . . . . . . . . . . . . . . . . . . . .
6.3.1 Linguagem de Mquina - Arquivo executvel . . . . . . . . .
6.3.2 Linguagem de Mquina - Arquivo objeto . . . . . . . . . . .
6.3.2.1 Bibliotecas . . . . . . . . . . . . . . . . . . . . . .
6.3.2.2 Bibliotecas Estticas . . . . . . . . . . . . . . . . .
6.3.2.3 Bibliotecas Compartilhadas . . . . . . . . . . . . .
6.3.2.4 Bibliotecas Dinmicas . . . . . . . . . . . . . . . .
6.4 O Programa Ligador . . . . . . . . . . . . . . . . . . . . . . . . . .
6.4.1 Arquivos objeto com um nico segmento . . . . . . . . . . .
6.4.2 Arquivos objeto com mais de um segmento . . . . . . . . . .
6.4.3 Detalhes Importantes . . . . . . . . . . . . . . . . . . . . . .
6.4.4 Exemplo Prtico . . . . . . . . . . . . . . . . . . . . . . . .
6.4.5 Ligadores para objetos compartilhados . . . . . . . . . . . .
6.4.6 Ligadores para objetos dinmicos . . . . . . . . . . . . . . .
6.5 O Programa Carregador . . . . . . . . . . . . . . . . . . . . . . . . .
6.5.1 Carregadores que copiam os programas em memria fsica sem
relocao . . . . . . . . . . . . . . . . . . . . . . . . . . . .
6.5.2 Carregadores que copiam os programas em memria fsica com
relocao . . . . . . . . . . . . . . . . . . . . . . . . . . . .
6.5.3 Carregadores que copiam os programas em memria virtual .
6.5.3.1 Executveis com Bibliotecas Estticas . . . . . . .
6.5.3.2 Executveis com Bibliotecas Compartilhadas . . . .
6.5.3.3 Executveis com Bibliotecas Dinmicas . . . . . .
6.5.4 Emuladores e Interpretadores. . . . . . . . . . . . . . . . . .
6.5.4.1 Emuladores . . . . . . . . . . . . . . . . . . . . .
6.5.4.2 Interpretadores . . . . . . . . . . . . . . . . . . . .

75
75
76
78
78
79
80
80
82
83
86
86
90
90
91
95
96
97

99
99
100
100
101
101
101
102

A Formatos de Instruo
A.1 Modos de Endereamento . . . . . . . . . . . . . . . . . . . . . . . .
A.2 Registrador . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
A.3 Imediato . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

105
106
106
107

97

CONTEDO

A.4 Endereamento Direto . . . . . . . . . . . . . . . . . . . . . . . . .


A.5 Endereamento Indireto . . . . . . . . . . . . . . . . . . . . . . . . .
A.6 Endereamento Indexado . . . . . . . . . . . . . . . . . . . . . . . .

107
108
108

B MMX
B.1 Como verificar a presena do MMX . . . . . . . . . . . . . . . . . .
B.2 Exemplo de aplicao paralela . . . . . . . . . . . . . . . . . . . . .

111
111
112

C Memria Virtual
C.1 Overlay . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
C.2 Paginao . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
C.2.1 MMU . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

115
115
116
121

CONTEDO

Parte I

Traduo e Execuo de
Programas

11
Nesta parte do livro ser descrito o que acontece ao longo do tempo de vida de um
processo2, ou seja, desde que ele inicia sua execuo at ele terminar ou ser cancelado.
A forma de execuo de um programa depende fundamentalmente do processador e
do sistema operacional adotado. Como existem vrios processadores e vrios sistemas
operacionais, uma combinao deles foi adotada e todos os exemplos utilizados seguem
esta combinao.
Como processador foi escolhida a famlia de processadores x86 para 32 bits, tambm conhecido como IA32. Grande parte dos computadores disponveis comercialmente utlizam este processador, o que garante maior facilidade para reproduzir a execuo dos programas descritos no livro.
Como sistema operacional foi escolhido o linux. Esta escolha se deve a vrios
fatores, dos quais destacamos:
1. o cdigo fonte do sistema operacional pode ser obtido e estudado;
2. o formato dos arquivos objeto e executveis, assim como o modelo de execuo
livre e bem documentado (formato ELF3 ).
A execuo de programas ser analisada em um nvel prximo CPU, e por esta
razo, os programas usados para apresentar o modelo de execuo foram desenvolvidos
em linguagem assembly. O assembly utilizado em todos os exemplos do livro foram
codificados, montados e ligados em um sistema operacional linux 2.6.38, distribuio
ubuntu 11.04.
O sistema operacional linux apresenta tambm algumas vantagens didticas, uma
vez que um programa em execuo recebe uma grande quantidade de memria virtual
(4 gigabytes para mquinas de 32 bits), onde os endereos no variam de uma execuo
para outra.
A forma de mapear os endereos da memria virtual para a memria fsica do
computador ocorre atravs de um mecanismo chamado paginao.
A figura 1 mostra um programa em execuo em memria (virtual). Observe que
o programa em execuo composto por vrias partes (chamadas sees): seo text,
data, bss, stack, e reas de uso exclusivo do sistema operacional.
A partir da figura, possvel ver que um programa em execuo recebe 4 Gigabytes de memria virtual para trabalhar. Nem toda esta memria pode ser acessada livremente pelo processo, como por exemplo o espao na parte de baixo, e o espao na
parte de cima:
0x00000000 e 0x08048000 ((134.512.640)10 bytes, um pouco mais de 128Mbytes;
0xBFFFFFFF e 0xFFFFFFFF ((1.073.741.824)10 bytes, ou 1Gbytes).
Com isso, do espao virtual de 4Gbytes citado acima, cada programa pode usar, na
prtica, um pouco menor, um pouco menos de 3Gbytes.
2 processo

o termo usado para referenciar um programa em execuo.


and Linkable Format

3 Executable

12

Figura 1: Programa em execuo na memria virtual (formato ELF)


Alm destas duas reas, o processo tambm no pode acessar a rea entre a seo
bss e a stack. Se o processo tentar acessar qualquer destas trs reas, o programa
ser cancelado por tentativa de acesso a endereo invlido, resultando na simptica
mensagem Segmentation Fault.
Cada seo ser abordada em captulos diferentes do livro. O captulo 1 descreve
a seo de cdigo (text), e de dados globais (data). O captulo 2 descreve como o
programa em execuo utiliza a pilha para implementar chamadas de procedimento,
assim como alocar espao para parmetros e variveis locais. O captulo 4 descreve
como alocar espao para variveis dinmicas. Por fim, o captulo 3 descreve como o
processo pode acessar recursos (como por exemplo arquivos) que no esto em sua
rea de endereamento virtual utilizando chamadas de sistema.
O mtodo de apresentao baseia-se na traduo de pequenos programas que esto
na linguagem C para programas assembly dos IA32 com funcionalidade equivalente
aos programas em C.
importante destacar os termos com funcionalidade equivalente. Isto significa
dizer que o programa assembly produzir os mesmos resultados que o programa C
geraria, porm no obrigatoriamente com os mesmos comandos, e nem com os mesmos
endereos de variveis. Alm disso, se o programa em C for compilado, o cdigo
gerado pode apresentar diferenas significativas.
Programas assembly so programas com muitas linhas de cdigo, e quase sempre
difceis de serem compreendidos por seres humanos (ou pelo menos pela maioria deles). Este livro no tem a inteno de ensinar a linguagem assembly, e os exemplos so
apresentados unicamente para descrever o que ocorre durante a execuo de programas.
Por esta razo, no objetivo do livro entrar em muitos detalhes e muito menos
de apresentar o conjunto completo de instrues contido na famlia IA324 . Ser des4 o que seria desumano, pois existem

algumas centenas de instrues com formato de tamanhos diferentes.

13
crito somente um subconjunto mnimo de instrues para os fins propostos pelo livro.
Este subconjunto exclui vrias instrues e formatos de instruo, sendo que alguns
programas so mais longos do que poderiam ser.
Este subconjunto no apresentado de uma vez, mas sim de forma incremental.
Cada exemplo inclui alugumas instrues ao conjunto apresentado at aquele momento, seguindo o modelo de [Kow83].

14

Captulo 1

A Seo de Cdigo e de Dados


Este captulo descreve a seo do programa em execuo que contm as instrues
do programa, chamada de seo de texto ou text. Incidentalmente tambm descreve a
alocao de variveis globais (seo de dados ou data), finalizando com vetores.
A parte do programa em execuo abordado por este captulo est destacado na
figura 1.1. Esta figura representa a rea virtual de um programa em execuo, e alguns
detalhes devem ser destacados:
Como este livro baseia-se em mquinas de 32 bits, o espao de endereamento
vai do endereo 0x00000000 (em baixo) at o endereo0xFFFFFFFF (em
cima). So 4 giga bytes.
a rea de cdigo comea no endereo virtual 0x08048000, e as instrues so
copiadas do arquivo executvel para esta rea.
a rea de dados comea logo acima da rea de cdigo. O incio desta seo
no fixo como a rea de cdigo, e depende do tamanho desta. Quanto maior
o tamanho da rea de cdigo, mais alto ser o endereo de incio da seo de
dados.
O captulo est organizado da seguinte forma: a seo 1.1 apresenta o modelo de
um programa em assembly, um esqueleto de programa assembly. Este esqueleto
recheado nas sees seguintes. A seo 1.2 descreve como traduzir expresses
aritmticas utilizando variveis globais. A seo 1.3 apresenta o funcionamento de comandos de desvio de fluxo em linguagem assembly, enquanto que a seo 1.4 mostra
como traduzir comandos do tipo while. A seo 1.5 descreve como traduzir comandos condicionais do tipo if-then e if-then-else). Para finalizar, a seo 1.6
contm um exemplo que combina comandos repetitivos e condicionais que trabalham
sobre vetores em assembly.
15

16

CAPTULO 1. A SEO DE CDIGO E DE DADOS

Figura 1.1: Programa em execuo na memria virtual - rea de texto e dados globais
(formato ELF)

1.1 Esqueleto de programas em assembly


Este captulo apresenta um programa escrito em linguagem C, semelhante ao algoritmo 35), cuja nica funo terminar graciosamente. O programa tem poucos comandos, e o objetivo mostrar o esqueleto de um programa traduzido de C para assembly, e posteriormente alguns aspectos da execuo de programas. A traduo do
algoritmo 1 apresentada em 2.
1
2
3
4

main ( int argc, char** argv);


{
return 13;
}
Algoritmo 1: Arquivo prog1.c

No algoritmo 1, o nico comando o da linha 3, return 13. Este comando


finaliza o programa, e retorna o nmero 13, que pode ser visto atravs do comando
echo $? digitado no terminal.
A traduo acima no literal, uma vez que o main uma funo e deveria ter sido
traduzida como tal (como ser visto no captulo sobre traduo de funes). Porm,
uma traduo vlida, uma vez que a funcionalidade do programa assembly equivale
funcionalidade do programa em C.
Programas em assembly so divididos em sees, e o programa traduzido contm
duas sees. A seo .data contm as variveis globais do programa (como este programa no usa variveis globais, esta seo est vazia) enquanto que a seo .text
contm os comandos a serem executados quando o programa for colocado em execu-

1.1. ESQUELETO DE PROGRAMAS EM ASSEMBLY

1
2
3
4
5
6
7

17

.section .data
.section .text
.globl _start
_start:
movl $1, %eax
movl $13, %ebx
int $0x80
Algoritmo 2: Arquivo prog1.s

o.
A linha 4 do algoritmo 2 contm um rtulo, ou seja, um nome associado a um
endereo. Na sintaxe do assembly que estamos usando (como em vrios outros), o
rtulo sempre um conjunto de caracteres e letras terminadas por dois pontos ( :).
O rtulo _start especial e deve sempre estar presente. Ele corresponde ao
endereo da primeira instruo do programa que ser executada, e deve ser declarada
como global (linha 3). As demais instrues sero executadas na seqncia.
Os comandos necessrios para gerar o arquivo executvel esto descritos a seguir:
> as prog1.s -o prog1.o
> ld prog1.o -o prog1
> ./prog1
> echo \$?
13

A execuo no mostra o que ocorre durante a execuo. Para tal, utilizaremos


o programa ald (assembly language debugger), para executar o programa, passo a
passo1
Considere a figura ??. Esta figura mostra esquematicamente um computador. Os
trs blocos correspondem CPU, memria e dispositivos perifricos de I/O. Estes trs
blocos esto ligados pelo barramento, que usado para a comunicao destes trs blocos.
Para simplificar as coisas, considere que a memria contm o processo em execuo, e s ele.
A execuo do programa que est na memria envolve um ciclo:
1. busca da prxima instruo. A CPU indica qual o endereo da memria que
contm a prxima instruo a ser executada. A memria retorna o contedo
daquela posio de memria, ou seja, a instruo.
2. decodificao da instruo. Ao chegar na CPU, a instruo est em um formato
binrio, e a CPU deve descobrir qual a instruo presente. Este processo
chamado de decodificao de instruo.
3. execuo da instruo. A CPU executa a instruo.
1 Tambm

pode ser usado o gdb (gnu debugger), que muito mais poderoso e com muitas funcionalidades a mais. Tantas que podem desviar o foco do leitor.

CAPTULO 1. A SEO DE CDIGO E DE DADOS

18

Os dois primeiros passos do ciclo implicam em usar o barramento para que a CPU
e a memria possam se comunicar.
Dentro da CPU existem estruturas de armazenamento de dados chamados registradores. A quantidade, tamanho e nome dos registradores so particulares de cada CPU.
Alguns registradores so de propsito geral (podem ser usados livremente) e outros so
de propsito especfico.
No caso dos processadores da famlia IA32, os principais registradores de propsito
geral so: eax, ebx, ecx e edx. Alguns dos registradores de propsito especfico so
eip (contm o endereo da prxima instruo), esp (contm o endereo do topo da
pilha) e ebp (auxiliar para acesso aos registros de ativao.
Existem vrios outros registradores, porm para manter um enfoque minimalista,
este livro s ir trabalhar com estes.
Para ver o ciclo de execuo de instrues em ao, deve-se usar o simulador ald.
O primeiro passo , na linha de prompt, digitar ald, e depois das mensagens iniciais digitar load prog1. Isto far com que o programa indicado seja colocado na
memria para iniciar a simulao da execuo.
A primeira instruo est para ser executada, porm o simulador no a mostra. Para
visualiz, necessrio indicar um ponto de parada (breakpoint). A sequncia abaixo
indica o resultado que eu obtive:
> ald
> Assembly Language Debugger 0.1.5a
Copyright (C) 2000-2003 Patrick Alken
ald> load "prog1"
prog1: ELF Intel 80386
ald> step
eax = 0x00000001 ebx =
esp = 0xFF864DF0 ebp =
ds = 0x0000002B es =
ss = 0x0000002B cs =

(32 bit), LSB - little endian, Executable, Version 1 (Current)


0x00000000
0x00000000
0x0000002B
0x00000023

ecx
esi
fs
eip

=
=
=
=

0x00000000
0x00000000
0x00000000
0x08048059

edx = 0x00000000
edi = 0x00000000
gs = 0x00000000
eflags = 0x00000202

Flags: IF

08048059
ald>

BB0D000000

mov ebx, 0xd

Comandos executados dentro do ald:


load prog1: carrega o arquivo executvel prog1 para o simulador.
step: executa uma nica instruo. Esta instruo ser aquela contida no endereo do rtulo _start.
Aps o step, a instruo executada (movl $1, %eax), que copia a constante
$1, para dentro de %eax (veja o valor de %eax aps o step).
Observe que aps o step, o valor de todos os registradores so impressos (eax,
ebx, ecx, edx, esp, ebp, esi, edi, ds, es, fs, gs, ss, cs,
eip, eflags).
Vamos usar a prxima instruo para mostrar o ciclo de execuo:

1.2. EXPRESSES ARITMTICAS

19

Busca de instruo a prxima instruo est indicada na ltima linha 08048059


BB0D000000 mov ebx, 0xd2 . O primeiro campo indica o endereo da
prxima instruo. o mesmo valor que est contido em %eip. O segundo
campo indica o padro binrio daquela instruo, e o terceiro campo indica a
instruo assembly contida naquele padro binrio. normalmente aqui que o
%eip alterado para apontar para a prxima instruo.
Decodificao da instruo O padro binrio citado acima (que neste exemplo BB0D000000)
que contm a instruo a ser executada. Dentro deste padro esto contidas as
informaes sobre o que deve ser executado (copiar uma constante para o registrador), qual a constante (0xd16 = 1010 ) a ser copiada e qual o registrador
destino %ebx.
Execuo A instruo executada.
Cada vez que se digita step no simulador, este ciclo se repete, e as instrues do
programa so executadas uma a uma.
Um comando importante do ald o examine. Este comando imprime o contedo da memria virtual do processo a partir do endereo indicado.
Como exemplo, considere o comanod examine 0x08048059 (valor corrente
de %eip). A impresso que obtive mostrada abaixo:
...
ald> examine 0x08048059
Dumping 64 bytes of memory starting at 0x08048059
08048059: BB 0D 00 00 00 CD 80 00 2E 73 79 6D 74
08048069: 2E 73 74 72 74 61 62 00 2E 73 68 73 74
08048079: 62 00 2E 74 65 78 74 00 00 00 00 00 00
08048089: 00 00 00 00 00 00 00 00 00 00 00 00 00

in
61
72
00
00

hex
62 00
74 61
00 00
00 00

.........symtab.
.strtab..shstrta
b..text.........
................

Este endereo inicia com os valores hexadecimais 0xBB 0D 00 00, que corresponde exatamente ao padro hexadecimal da instruo movl 10, %ebx.
Como este livro mostra o que ocorre em tempo de execuo, em especial na espao
virtual de endereamento, o comando examine passa a ser muito til.

1.2 Expresses Aritmticas


Esta seo mostra como traduzir para assembly os programas escritos em linguagem C
que contm somente expresses aritmticas que usam variveis globais.
O algoritmo 3 um programa em linguagem C que utiliza duas variveis globais (a
e b). Estas variveis so somadas e o resultado colocado para o usurio (na varivel
de ambiente $? do shell).
2 Observe que a ordem dos parmetros est invertida quando comparada com o programa. Isto ocorre
porque h dois tipos de formado de programas assembly para os IA32: o formato Intel e o formato AT&T. O
programa foi escrito obedecendo o formato AT&T (prprio para o montador as, desenvolvido pelo mesmo
grupo que implementou o programa gcc (gnu), enquanto que o ald apresenta comandos no formato Intel.
Ambos os formatos so equivalentes, e a maior diferena entre eles a ordem dos parmetros. Os programas
que apresentaremos neste texto esto sempre no formato AT&T.)

CAPTULO 1. A SEO DE CDIGO E DE DADOS

20

1
2
3
4
5
6
7
8

1
2
3
4
5
6
7
8
9
10
11
12
13

int a, b;
main ( int argc, char** argv);
{
a=6;
b=7;
a = a+b;
return a;
}
Algoritmo 3: Arquivo prog2.c

.section .data
a: .int 0
b: .int 0
.section .text
.globl _start
_start:
movl $6, a
movl $7, b
movl a, %eax
movl b, %ebx
addl %eax, %ebx
movl $1, %eax
int $0x80
Algoritmo 4: Arquivo prog2.s

1.2. EXPRESSES ARITMTICAS

21

O algoritmo 4 corresponde ao programa assembly equivalente ao programa C do


algoritmo 3.
O primeiro aspecto a ser levantado como as variveis globais a e b foram
declaradas no programa assembly (linhas 2 e 3 do algoritmo 4, ou seja, como rtulos
(veja a definio de rtulo na pgina 17).
Estes dois rtulos foram declarados na seo .data. Quando o programa for colocado em execuo, sero reservados dois espaos de inteiro (para isto o .int das
linhas 2 e 3 do algoritmo 4). O parmetro que vem aps o .int indica o valor inicial
da varivel, que no caso zero.
Para deixar este ponto mais claro, vamos verificar como o programa ser executado.
Aps montar e ligar o programa, obteremos o arquivo executvel prog2. Vamos utilizar
novamente o simulador ald e analisar a segunda instruo (linha 8).
0804807E C705A090040807000000 mov dword [+0x80490a0], 0x7
O primeiro valor impresso, 0804807E, corresponde ao endereo onde est localizada esta instruo enquanto que C705A090040807000000 corresponde ao cdigo da instruo, e operandos, decodificados ao lado (mov dword [+0x80490a0],
0x7). Ao ser executada, esta instruo ir colocar a constante 0x7 no endereo
0x80490a0.
Ao examinar o algoritmo 4, consta-se que esta instruo em cdigo de mquina
corresponde instruo da linha 8, ou seja, movl $7, b.
Desta forma, possvel deduzir que, em tempo de execuo, o endereo de b
0x80490a0.
Como ser visto adiante, quem define o endereo de a e de b o ligador, e no
o montador e nem o carregador. Para comprovar isso, observe o resultado abaixo, do
contedo do arquivo objeto (gerado pelo montador).
> objdump prog2.o -S
prog2.o:

file format elf32-i386

Disassembly of section .text:


00000000
0: c7
7: 00
a: c7
11: 00
14: a1
19: 8b
1f: 01
21: b8
26: cd

<_start>:
05 00 00 00 00 06
00 00
05 04 00 00 00 07
00 00
00 00 00 00
1d 04 00 00 00
c3
01 00 00 00
80

movl

$0x6,0x0

movl

$0x7,0x4

mov
mov
add
mov
int

0x0,%eax
0x4,%ebx
%eax,%ebx
$0x1,%eax
$0x80

Compare este resultado com o contedo do arquivo executvel (gerado pelo ligador):
> objdump -S prog2
prog2:
file format elf32-i386
Disassembly of section .text:

CAPTULO 1. A SEO DE CDIGO E DE DADOS

22

08048074 <_start>:
8048074: c7 05 9c
804807b: 00 00 00
804807e: c7 05 a0
8048085: 00 00 00
8048088: a1 9c 90
804808d: 8b 1d a0
8048093: 01 c3
8048095: b8 01 00
804809a: cd 80

90 04 08 06

movl

$0x6,0x804909c

90 04 08 07

movl

$0x7,0x80490a0

04 08
90 04 08

mov
mov
add
mov
int

0x804909c,%eax
0x80490a0,%ebx
%eax,%ebx
$0x1,%eax
$0x80

00 00

Em especial, examine a instruo movl $6, a. No arquivo objeto, temos // movl $0x6,0x0 enquanto que no arquivo executvel, temos movl $0x6,0x804909c. No arquivo objeto, a referncia ao
smbolo a zero (0x0), que s uma referncia. Algo como um aviso ao ligador: este smbolo deve
ser alocado no primeiro endereo disponvel da regio de dados. Quando o ligador gera o executvel, ele
observa isso e aloca este smbolo no endereo virtual disponvel na seo de dados, ou seja, 0x804909c.
J o smbolo b no arquivo objeto est indicado para ser alocado em $0x4, ou seja, quatro bytes depois
de a. O ligador ento aloca-o em 0x80490a0 = 0x804909c + 0x4.
Os processadores da famlia IA32 definem tamanhos para os tipos fundamentais de dados, em especial:
bytes (8 bits), words (16 bits), double words (32 bits) quadwords (64 bits) e double quadwords (128 bits).
Alis, o termo varivel em assembly diferente do termo utilizado em linguagens de programao
fortemente tipadas (como Pascal), onde cada varivel de um tipo e deve passar por alguma operao
especial para ser convertida para outro tipo. Nestas linguagens, uma varivel do tipo inteiro no pode ser
somada a uma varivel real (apesar de ambas ocuparem 32 bits).
J no caso de assembly, as variveis no esto associados a nenhum tipo (.int indica que deve ser alocado
espao necessrio para um inteiro, ou seja, 32 bits). Isto significa que tanto a quanto b podem ser usadas
com instrues sobre .int, words, etc. Nem o montador nem o ligador fazem checagem de tipo - isto
responsabilidade do programador (e volta e meia do uma grande dor de cabea).
Outro ponto importante que a instruo de soma utilizada, addl, soma o contedo de dois registradores (neste caso %eax e %ebx, jogando o resultado em %ebx. Esta operao soma dois inteiros de 32 bits
com ou sem sinal. O caso de haver overflow (em qualquer um dos dois casos) indicado no registrador
EFLAGS.
Para outros tipos, formatos de somas, e outras instrues para operaes aritmticas, veja [Int04b].

1.3 Comandos Repetitivos


Comandos repetitivos so aqueles que permitem que um conjunto de instrues seja repetido at que uma
determinada condio ocorra. Em linguagens de alto nvel, normalmente so divididos em comandos do tipo
repeat e comandos do tipo while. Um repeat indica que o conjunto de instrues pode ser repetido de
um a vrias vezes, enquanto que um while indica que o conjunto de instrues pode ser repetido de zero a
vrias vezes. A forma de operao destas construes est apresentada na figura 1.2.
H tambm a construo do tipo for, que indica que um conjunto de instrues deve ser repetido um
determinado nmero de vezes (figura 1.3). Esta construo semelhante ao comando while, porm acrescida
de uma varivel de controle e de:
1. um comando para iniciar a varivel de controle antes do primeiro teste;
2. um comando para acrescentar a varivel de controle de um aps a execuo da seqncia de comandos especificada;
3. que a expresso verifica se o valor da varivel de controle ultrapassou o limite.
Em linguagens assembly, estas construes no esto presentes, o nosso prximo passo descrever
como traduzir comandos de alto nvel para comandos de baixo nvel. Esta traduo feita automaticamente pelos compiladores, mas nem sempre so exatamente como descritas aqui principalmente para
melhorar o desempenho.

1.3. COMANDOS REPETITIVOS

Figura 1.2: Funcionalidade das construes while e repeat.

Figura 1.3: Funcionalidade da construo for.

23

CAPTULO 1. A SEO DE CDIGO E DE DADOS

24

Com as instrues descritas at aqui, seria impossvel repetir o funcionamento de comandos repetitivos.
Afinal, todas as instrues vistas at aqui indicam (implicitamente) que a instruo seguinte a prxima a
ser executada.
Para tal, existem instrues assembly especficas que desviam o fluxo de execuo indicando o destino
para onde desviar usando rtulos. Como exemplo, considere o algoritmo 5, que incrementa o valor em %eax
de um at ultrapassar o valor de %ebx. Este programa tem trs rtulos (_start, loop, fim_loop) e
duas instrues de desvio (jg e jmp), que esto para desvia se maior (jump if greater) e desvio incondicional (jump) respectivamente.
O rtulo _start, como j vimos, indica o local onde a execuo do programa deve iniciar. O rtulo
loop indica o ponto de incio do lao e o rtulo fim_loop indica o ponto de sada do lao, que ser
executada assim que a condio de trmino for atingida.

1
2
3
4
5
6
7
8
9
10
11
12
13

.section .text
.globl _start
_start:
movl $0, %eax
movl $10, %ebx
loop:
cmpl %ebx, %eax
jg fim_loop
add $1, %eax
jmp loop
fim_loop:
movl $1, %eax
int $0x80
Algoritmo 5: Arquivo rot_e_desvios.s

Alm das instrues de desvio, o programa apresenta um outro comando novo: cmpl. Esta instruo
compara o SEGUNDO argumento com o primeiro (no caso, %eax com %ebx) colocando o resultado em
um bit de um registrador especial (EFLAGS). Este registrador afetado por vrios tipos de instruo, e
contm informaes sobre a ltima instruo executada, como por exemplo se ocorreu overflow aps uma
soma.
Os bits deste registrador podem ser testados individualmente, e no caso da operao jump if greater,
verifica se o bit ZF (zero flag) igual a zero e se SF=OF (SF=Sign Flag e OF=Overflow Flag). Para mais
detalhes sobre o funcionamento do EFLAGS, veja [Int04a].
Observe que se alguma outra instruo for colocada entre a comparao (cmpl) e o desvio os bits do
EFLAGS podem sero afetados por esta nova instruo. Isto implica dizer que a instruo de desvio jg deve
ser colocada imediatamente aps a instruo cmpl.
Outras instrues de desvio que sero utilizadas ao longo deste livro so:
jge (jump if greater or equal),
jl (jump if less),
jle (jump if less or equal),
je (jump if equal),
jne (jump if not equal).
Assim como a instruo jg, as instrues acima tambm funcionam analisando os bits de EFLAGS.
Outro aspecto importante a ser destacado neste programa que ele segue a funcionalidade do comando
for. Compare o programa com a figura 1.3, e observe que no desenho, o fim do loop atingido quando a
condio for falsa (ou seja, quando a varivel de controle no mais menor ou igual ao limite). No programa
acima, o teste se a condio de fim de loop foi atingida (ou seja, que a varivel de controle maior do que
o limite).

1.4. TRADUO DA CONSTRUO WHILE

25

Como exerccio, simule a execuo do programa acima, prestando ateno a registrador EIP (extended
instrucion pointer). Fiz isto em meu simulador, e obtive o seguinte resultado:

1
2
3
4
5
6
7
8
9
10

%eip
08049074
08049079
0804907E
08049080
08049082
08049085
0804907E
08049080
08049082
08049085
...

instruo (hexa)
B800000000
BB0A000000
39D8
7F05
83C001
EBF7
39D8
7F05
83C001
EBF7
...

instruo
movl 0x0, %eax
movl 0xa, %ebx
cmpl %eax, %ebx
jg fim_loop
addl %eax, 0x1
jmp loop
cmpl %eax, %ebx
jg fim_loop
addl %eax, 0x1
jmp loop
...

Cada linha contm exatamente uma instruo. A coluna da esquerda indica a ordem de execuo das
instrues. A coluna %eip indica o endereo da instruo, e a terceira coluna indica o contedo daquele endereo (a instruo em hexadecimal). A ltima coluna mostra o cdigo mnemnico da instruo hexadecimal
indicada.
Desta forma, a primeira instruo executada estava no endereo 0x08049074 (endereo do rtulo
_start). A instruo contida naquele endereo movl 0x0,%eax, cujo cdigo de mquina (em hexadecimal), 0xB800000000.
Como esta instruo ocupa cinco bytes, a prxima instruo dever estar localizada em 0x08049074
+ 0x00000005 = 0x08049079. Este o valor de %eip ao longo da execuo desta instruo (o %eip
sempre aponta para a prxima instruo na seqncia). Para calcular o endereo da prxima instruo, basta
somar o endereo da instruo corrente com o tamanho desta instruo.
Esta lgica de funcionamento quebrada quando ocorre um desvio. Observe o que ocorre aps a
instruo jmp loop. A instruo seguinte est no endereo 0x0804907E, que no coincidentemente o
mesmo endereo do rtulo loop. Este endereo determinado em tempo de ligao (ou seja, pelo ligador),
e pode ser observado no cdigo executvel (verifique com o comando objdump).
Com este exemplo possvel verificar na prtica que rtulos so utilizados para indicar endereos que
tem alguma importncia para o programa.

1.4 Traduo da construo While


A traduo de uma construo while segue o fluxo de execuo apresentado na figura 1.2, onde os desvios
no fluxo so substitudos por comandos de desvio em assembly e os pontos de entrada dos desvios so
substitudos por rtulos.
Como exemplo, considere os algoritmos 6 e 7.

1
2
3
4
5
6
7
8
9

main ( int argc, char** argv)


{
...
while ( E )
{
C1 , C2 , . . . Cn ;
}
...
}
Algoritmo 6: Comando while (Linguagem de alto nvel).

26

1
2
3
4
5
6
7
8
9
10
11

CAPTULO 1. A SEO DE CDIGO E DE DADOS


.section .text
.globl _start
_start:
...
while:
Traduo da Expresso (E)
jFALSO fim_while
Traduo dos Comandos (C1 , C2 , . . . Cn )
jmp while
fim_while:
...
Algoritmo 7: Traduo do comando while da figura 6 para assembly.

As instrues do algoritmo 7 que esto em negrito correspondem s instrues que do a funcionalidade


do comando while (ou seja, os comandos que implementam a repetio de comandos at que a expresso
seja falsa).
Os comandos que precedem o while (os pontilhados da linha 3 do algoritmo 6) so traduzidos na seqncia em que aparecem, e esto representados na linha 4 do algoritmo 7. De forma anloga se traduz os
comandos de sucedem o while.
Quando o incio do while encontrado (linha 4 do algoritmo 6), arquivo assembly recebe um rtulo
while:, que indica o incio da construo. Em seguida, traduz-se a expresso, e o fluxo ser desviado
para o rtulo fim_while (linhas 5-7 do algoritmo 7) se o resultado da expresso for falso. O desvio da
linha 6 (jFALSO) deve ser substituda por algum dos comandos de desvio (vrios destes so apresentados na
pgina 24).
Os comandos que compe o while (linha 6 do algoritmo 6) so traduzidos para assembly na seqencia
em que aparecem. Aps o ltimo destes comandos, a traduo apresenta uma instruo de desvio incondicional (linha 9 do algoritmo 7) para o rtulo while (linha 4). Isto significa que a instruo seguinte a ser
executada aquela contida no rtulo while (traduo da expresso).
Como exemplo de traduo de um programa completo, considere os algoritmos 8 e 9.

1
2
3
4
5
6
7
8
9
10
11

int i, a;
main ( int argc, char** argv)
{
i=0; a=0;
while ( i<10 )
{
a+=i;
i++;
}
return (a);
}
Algoritmo 8: Programa while.c

H um aspecto importante ser levantado no programa traduzido (algoritmo 9): o acesso s variveis.
As variveis globais i e a do programa em linguagem C foram mapeados para rtulos no arquivo
assembly.
Quando ocorre uma atribuio a alguma varivel de um programa de alto nvel, esta atribuio mapeada para o endereo da varivel correspondente (neste caso, varivel global). Porm, como o acesso

1.5. COMANDOS CONDICIONAIS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

27

.section .data
i: .int 0
a: .int 0
.section .text
.globl _start
_start:
movl $0, i
movl $0, a
movl i, %eax
while:
cmpl $10, %eax
jge fim_while
movl a, %ebx
addl %eax, %ebx
movl %ebx, a
addl $1, %eax
movl %eax, i
jmp while
fim_while:
movl $1, %eax
int $0x80
Algoritmo 9: Traduo do programa while.c (algoritmo 8) para assembly.

memria mais lento do que o acesso a registradores, mais eficiente mapear as variveis em registradores
e minimizar os acessos memria.
Desta forma, podemos construir um programa equivalente ao programa contido no algoritmo 9 ao considerar que i est mapeado no registrador %eax e que a est mapeado no registrador %ebx. Desta forma,
podemos eliminar as linhas 2, 3, 6, 7, 8, 12, 14 e 16, para obter o algoritmo 10. Este programa equivalente
ao programa do algoritmo 9, porm como ele s acessa registradores, mais rpido.
Um dos desafios da traduo de programas escritos em linguagens de alto nvel para programas em
linguagem assembly maximizar a quantidade de variveis mapeadas em registradores, e com isso melhorar
o desempenho do programa. Os compiladores so capazes de fazer esta tarefa muito bem, porm o resultado
final depende muito da quantidade de registradores que esto disponveis na arquitetura alvo. Por esta razo,
a maior parte dos processadores modernos so projetados com um grande nmero de registradores, uma vez
que quando o nmero de registradores pequeno (como por exemplo nos processadores da famlia x86 que
s tem oito registradores de propsito geral), a tendncia que programas que usam muitas variveis tenham
um desempenho inferior daquele obtido em processadores com muitos registradores. importante observar
que cada vez mais incomum encontrar programas teis que usam poucas variveis.
Uma soluo freqente para melhorar o desempenho dos processadores anexar uma memria superrpida, bem prxima CPU (ou at dentro), chamada de memria cache, cuja capacidade de armazenamento
de dados muito superior capacidade de armazenamento de dados do processador e o tempo de acesso
no muito maior do que o acesso a registradores. Porm o inconveniente o custo, que muito superior
memria convencional. Por esta razo, as memrias cache so normalmente pequenas.

1.5 Comandos Condicionais


As linguagens de programao apresentam normalmente pelo menos dois tipos de comandos condicionais:
if-then e if-then-else. Se a expresso testada for verdadeira, a seqncia de comandos contida nos

28

CAPTULO 1. A SEO DE CDIGO E DE DADOS

.section .text
.globl _start
3 _start:
4 movl $0, %eax
5 while:
6
cmpl $10, %eax
7
jge fim_while
8
movl a, %ebx
9
addl %eax, %ebx
10
addl $1, %eax
11
jmp while
12 fim_while:
Algoritmo 10: Traduo do programa while.c (algoritmo 9) sem acessos memria.
1
2

comandos assembly relativos ao then deve ser executada, enquanto que se a condio for falsa, a seqncia
de comandos do else deve ser executada (se houver else, obviamente). Os comandos do then e else
no podem ser executados consecutivamente - ou executa um ou outro, mas nunca os dois.
O modelo de um comando condicional em C apresentado no algoritmo 11 e o arquivo assembly
correspondente apresentado no algoritmo 12.

1
2
3
4
5
6
7
8
9
10
11
12
13

main ( int argc, char** argv)


{
...
if ( E )
{
Cthen1 , Cthen2 , . . . Cthenn ;
}
else
{
Celse1 , Celse2 , . . . Celsem ;
}
...
}
Algoritmo 11: Comando If-Then-Else (Linguagem de alto nvel).

A traduo segue os moldes apresentados na estrutura repetitiva. A execuo depende do resultado


da expresso. Se a expresso for verdadeira, a seqncia de instrues incluir as linhas 5, 6, 7 e 12. Se
a expresso for falsa, a seqncia seguir as linhas 9, 10, 11, 12. No primeiro caso, sero executados os
comandos then e no segundo os comandos relativos a else. No h nenhuma forma de se executar os
comandos relativos a then e else, como manda o comando if.
Como exerccio, traduza o algoritmo 13 para assembly, monte e execute o programa atravs do ald.
Em seguida, altere os valores das variveis para que a execuo siga a outra alternativa. A idia com este
exerccio se familiarizar com todo o processo de gerao do programa executvel, com o fluxo de execuo,
e principalmente com o modelo de memria virtual que adotada no linux (endereo dos rtulos da seo
.data e da seo .text, endereo de incio do programa, etc.).

1.5. COMANDOS CONDICIONAIS

.section .text
.globl _start
3 _start:
4 ...
5
Traduo da Expresso (E)
6
jFALSO else
7
Traduo dos Comandos do then (Cthen1 , Cthen2 , . . . Cthenn ; )
8
jmp fim_if
9 else:
10
Traduo dos Comandos do else (Celse1 , Celse2 , . . . Celsen ; )
11 fim_if:
12 . . .
Algoritmo 12: Traduo do comando if-then-else do algoritmo 11 para assembly.
1
2

1
2
3
4
5
6
7
8
9
10
11
12
13
14

int a, b;
main ( int argc, char** argv)
{
a=4; b=5;
if (a>b) {
a=a+b;
return a;
}
else
{
a=a-b;
return a;
}
}
Algoritmo 13: Exemplo para traduo.

29

CAPTULO 1. A SEO DE CDIGO E DE DADOS

30

1.6 Vetores
Para finalizar o captulo, esta seo apresenta um exemplo que combina comandos condicionais e repetitivos.
Para aprofundar o conhecimento da seo de dados, este exemplo utiliza um vetor, e descreve as formas de
acessar este tipo de informao na memria virtual.
O programa apresentado no algoritmo 14, e contm um vetor (fixo) de dados. Este programa retorna
em $? o valor do maior elemento do vetor. A varivel maior armazena sempre este valor a cada iterao.
O vetor no tem um nmero fixo de elementos. Para determinar o fim do vetor usa-se um sentinela, ou
seja, um elemento do vetor com valor fixo (no caso, zero). Alm disso, pressupe-se que existe pelo menos
um elemento no vetor alm do sentinela.

1
2
3
4
5
6
7
8
9
10
11
12

int data_items[]={3, 67, 34, 222, 45, 75, 54, 34, 44, 33, 22, 11, 66, 0};
int i, int maior;
main ( int argc, char** argv)
{
maior = data_items[0];
for (i=1; data_items[i] != 0; i++)
{
if (data_items[i] > maior)
maior = data_items[i];
}
return (maior);
}
Algoritmo 14: Arquivo vetor.c
A traduo deste programa apresentada no algoritmo 15, e as novidades so:
1. a forma de criar um vetor com valores fixos. Basta list-los lado a lado na seo data, ao lado do
rtulo associado quele vetor (veja a linha 4 do algoritmo 15.
2. a forma de referenciar cada elemento do vetor: movl data_items(, %edi, 4), %ebx.
Este comando usa uma forma de endereamento especial, chamado endereamento indexado (veja
apndice A.6).

1.7 Exerccios
1. Proponha uma forma de traduo para os comandos for, switch e do .. while.
2. Altere o algoritmo15 para ordenar o vetor.

1.7. EXERCCIOS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

.section .data
i: .int 0
maior: .int 0
data_items: .int 3, 67, 34, 222, 45, 75, 54, 34, 44, 33, 22, 11, 66, 0
.section .text
.globl _start
_start:
movl $0, %edi
movl data_items(, %edi, 4), %ebx
movl $1, %edi
loop:
movl data_items(, %edi, 4), %eax
cmpl $0, %eax
je fim_loop
cmpl %ebx, %eax
jle fim_if
movl %eax, %ebx
fim_if:
addl $1, %edi
jmp loop
fim_loop:
movl $1, %eax
int $0x80
Algoritmo 15: Arquivo vetor.s

31

32

CAPTULO 1. A SEO DE CDIGO E DE DADOS

Captulo 2

A Seo da Pilha
A seo da pilha (stack) armazena informaes sobre chamadas de procedimento (parmetros, informaes
sobre contexto e variveis locais).
A idia de armazenar estas informaes em uma pilha originria da linguagem de programao Algol
601 . Antes desta linguagem todas as variveis eram globais e no havia procedimentos. Expoentes desta
poca foram as linguagens Fortran e Cobol, que posteriormente foram adaptadas para comportar procedimentos.
Antes de detalhar o tema, necessrio compreender o que ocorre em uma chamada de procedimento, ou
seja, o modelo utilizado para implementar chamadas de procedimento em praticamente todas as linguagens
de programao atuais.
A figura 2.1 mostra a parte do programa em execuo que ser abordado neste captulo, que corresponde
ao intervalo 0xbfffffff e o valor atual do registrador %esp. Existem instrues assembly para valores
na pilha e para retir-los de l que sero abordados na seqncia.

2.1 Modelo de Chamadas de Procedimento


Em termos abstratos, o modelo de chamadas de procedimento (independente de sistema operacional e de
arquitetura de computador), assemelha-se ao modelo de folhas de papel como descrito a seguir (baseado
em [Kow83]).
Quando um programa colocado em execuo, uma grande folha de papel (digamos uma folha em
formato A2) colocada sobre uma mesa. Nesta folha, escrevemos (por exemplo no canto superior esquerdo)
o nome de todas as variveis globais do programa. O valor de iniciao da varivel normalmente indefinido,
e qualquer atribuio a esta varivel se sobrepe ao valor que consta naquela folha.
Cada vez que um procedimento chamado, uma folha de papel menor (por exemplo, formato A5)
colocada sobre as demais (ou somente sobre a primeira) e nela escrito o nome do procedimento e todas
a variveis (parmetros e variveis locais) relativas quele procedimento. Assim como ocorre nas variveis
globais, as atribuies a variveis descritas naquela folha se sobrepe, ou seja, no alteram o valor anterior
(ou em outras folhas).
Atravs deste modelo fica mais fcil compreender o que ocorre quando da execuo de um procedimento
recursivo. Em cada chamada recursiva, as variveis a serem acessadas so somente aquelas da folha de papel
no topo da pilha (parmetros e variveis locais daquele procedimento) ou primeira folha de papel (variveis
globais).
Cada nova chamada de procedimento (ou seja, cada nova instncia da recurso), uma nova folha de
papel colocada sobre as anteriores, de tal forma que quando a execuo de uma determinada instncia da
1 As referncias no so definitivas, mas a idia aparentemente oriunda dos estudos do pesquisador
alemo Friedrich L. Bauer, que tambm trabalhou no desenvolvimento da linguagem, no uso da pilha para
clculos de expresses aritmticas e lgicas. Ele at ganhou um prmio de pioneirismo da IEEE ( IEEE
Computer Society Pioneer Award) em 1988 pelo seu trabalho.

33

34

CAPTULO 2. A SEO DA PILHA

Figura 2.1: Programa em execuo na memria virtual - rea da pilha (formato ELF)

recurso chegar ao fim, a folha de papel associada a ele (a folha de papel no topo) dever ser eliminada e
a folha de papel anterior (ou seja, da instncia recursiva anterior) sero utilizados, e as variveis voltam a
ter o valor que tinham antes de iniciar a instncia que foi finalizada (excesso feita a variveis passadas por
referncia, claro).
Considere a linguagem C. Quando uma varivel referenciada no programa, o primeiro lugar onde esta
varivel deve ser procurada na folha de papel do topo. Se no estiver l, deve ser procurada na folha de
variveis globais e se no estiver l tambm, a varivel no existe. Este trabalho no realizado em tempo
de execuo, mas sim em tempo de compilao, e isso explica o erro comum que gera uma mensagem de
erro indicando que uma determinada varivel no foi declarada.

2.2 Implementao do Modelo em Uma Arquitetura


O modelo genrico apresentado na seo anterior implementado de formas diferentes dependendo do conjunto Linguagem de programao/Sistema Operacional/CPU. Esta seo descreve uma possvel implementao para o conjunto Linguagem C/Linux/x86. Existem outras possveis implementaes, como a que
utlizada pelo compilador gcc, onde os endereos das variveis locais e dos parmetros so um pouco diferentes.
O primeiro aspecto importante a ser lembrado que os endereos das variveis e dos procedimentos
so definidas em tempo de compilao. Os endereos dos procedimentos podem ser fixos (so associados
a rtulos), o que no pode ser feito para as variveis de cada folha de papel. Estas variveis devem ser
acessadas indicando a folha de papel onde ela est contida. Desta forma, cada folha de papel deve ser
numerada e quando nos referirmos a uma determinada varivel devemos dizer o nmero da folha de papel
onde ela est embutida e qual a varivel em questo.
Alis, j est na hora de utilizarmos o nome correto para as folhas de papel. Para um processo em
execuo, cada folha de papel chamada de registro de ativao2 .
Um registro de ativao (figura 2.2) instanciado sempre na rea da pilha, e basicamente composto
por trs partes:
parmetros: local onde reservado espao para as variveis que correspondem aos parmetros de um
procedimento.
2 Em ingls activation register. Porm tambm referenciado como frame register, stack register, entre
outros.

2.2. IMPLEMENTAO DO MODELO EM UMA ARQUITETURA

35

Figura 2.2: Registro de Ativao.


informaes gerenciais: esta rea armazena basicamente o endereo da instruo de retorno e o valor
anterior do registrador que indica o registro de ativao anterior, mas pode armazenar ainda outras
informaes.
variveis locais: espao para as variveis do procedimento.
A seguir descrevemos como montar (e desmontar) os registros de ativao para cada procedimento em
tempo de execuo. Observe que os comandos devem ser calculados em tempo de compilao, porm o
efeito destas instrues (que implementam o registro de ativao) s poder ser visto em tempo de execuo.
A seo 2.2.1 descreve os registros de ativao sem parmetros e sem variveis locais. A seo 2.2.2
acrescenta as variveis locais ao modelo da seo anterior e a seo 2.2.3 acrescenta os parmetros ao modelo. A seo2.2.4 descreve parmetros passados por referncia e a seo 2.2.5 descreve alguns aspectos da
funo main da linguagem C. Para finalizar, a seo 2.2.6 apresenta um exemplo de uma funo recursiva,
que usa todos os conceitos apresentados e a seo 2.2.7 mostra como utilizar as funes disponveis em
bibliotecas, em especial na libc.

2.2.1 Sem Parmetros e sem Variveis Locais


De acordo com o modelo da figura 2.2, o registro de ativao de um programa que no tem parmetros e nem
variveis locais s contm as informaes gerenciais. Estas informaes, no caso da nossa arquitetura-alvo
corresponde ao endereo de retorno (ou seja, o endereo que o programa deve retornar quando finalizar a
execuo do procedimento) e o valor de um determinado registrador (%ebp) que usado para acessar as
variveis locais e os parmetros, mas que neste exemplo no usado para nada.
Para exemplificar como montar o registro de ativao neste caso, considere o algoritmo 16, traduzido
para assembly no algoritmo 17.
Este programa apresenta dois novos pares de instrues assembly: pushl e popl, call e ret. O par
pushl e popl insere e retira valores genricos da pilha, enquanto que call e ret insere e retira endereos
da pilha.

36

1
2
3
4
5
6
7
8
9
10
11
12

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

CAPTULO 2. A SEO DA PILHA

int a, b;
int soma ( )
{
return (a+b);
}
main ( int argc, char** argv)
{
a=4;
b=5;
b = soma();
return (b);
}
Algoritmo 16: Programa sem parmetros e sem variveis locais.

.section .data
A: .int 0
B: .int 0
.section .text
.globl _start
soma:
pushl %ebp
movl %esp, %ebp
movl A, %eax
movl B, %ebx
addl %eax, %ebx
movl %ebx, %eax
popl %ebp
ret
_start:
movl $4, A
movl $5, B
call soma
movl %eax, %ebx
movl $1, $eax
int $0x80
Algoritmo 17: Traduo do programa do algoritmo 16.

2.2. IMPLEMENTAO DO MODELO EM UMA ARQUITETURA

37

Comearemos descrevendo o par pushl e popl. A instruo pushl <registrador> empilha o


valor contido no registrador indicado, e equivalente s seguintes operaes: (1) subl $4, %esp e (2)
movl registrador, (%esp). J a instruo popl <registrador> faz a operao inversa, ou
seja: movl (%esp), <registrador> e addl $4, %esp. Observe que, como a pilha cresce para
baixo, necessrio decrementar %esp para inserir um elemento e incrementar para retir-lo.
O par call e ret anlogo, porm ao invs de empilhar/desempilhar o valor de um registrador qualquer, ele empilha/desempilha o valor do registrador %eip (instruction pointer), que indica qual o endereo
da prxima instruo a ser executada.
Desta forma, a instruo call <rotulo> tem efeito equivalente a executar as instrues pushl
%eip seguido de jmp <rotulo>, enquanto que ret tem efeito equivalente a popl %eip. Estas duas
instrues so a base para a chamada de procedimento, uma vez que o endereo da instruo que seria
executada aps a chamada empilhado com a instruo call e desempilhado e jogado em %eip na ltima
instruo do procedimento, e este ser o endereo da prxima instruo a ser executada.
A funo retorna um nmero inteiro, e este valor retornado em %eax. Este o padro para o x86, porm outras arquiteturas podem indicar outras formas. Para entender melhor como funciona todo o processo,
acompanhe a execuo do programa assembly usando o simulador.

2.2.2 Sem Parmetros e com Variveis Locais


Um aspecto que ainda no foi levantado corresponde s instrues das linhas 7, 8, e 13 do algoritmo 17.
Estas instrues salvam e atualizam o valor do registrador %ebp. O endereo contido neste registrador
sempre aponta para o mesmo local do registro de ativao corrente. Ao entrar em um novo procedimento,
sempre necessrio salvar o valor atual de %ebp e ao sair necessrio restaur-lo. A razo disso que este
registrador usado para acessar as variveis locais e as variveis que correspondem aos parmetros.
Observe que:
1. so sempre empilhadas duas informaes gerenciais
2. %ebp sempre aponta para uma mesma posio no registro de ativao (logo abaixo do endereo de
retorno e logo acima da primeira varivel local, se esta existir).
Como as variveis locais esto sempre logo abaixo do valor indicado por %ebp, indica-se o endereo
delas usando %ebp como referncia. Como exemplo, suponha que um programa contenha trs variveis
locais inteiras. Ento o endereo destas variveis ser: %ebp-4, %ebp-8 e %ebp-12. Para tal, necessrio
primeiro abrir espao para elas. Como exemplo, considere o algoritmo 18, cujo cdigo asssembly est
indicado no algoritmo 19.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

int a, b;
int soma ( )
{
int x, y;
x=a;
y=b;
return (x+y);
}
main ( int argc, char** argv)
{
a=4;
b=5;
b = soma();
return (b);
}
Algoritmo 18: Programa sem parmetros e com variveis locais.

38

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

CAPTULO 2. A SEO DA PILHA

.section .data
A: .int 0
B: .int 0
.section .text
.globl _start
_start:
soma:
pushl %ebp
movl %esp, %ebp
subl $8, %esp
movl A, %eax
movl %eax, -4(%ebp)
movl B, %eax
movl %eax, -8(%ebp)
addl -4(%ebp), %ebx
movl -8(%ebp), %eax
addl $8, %esp
popl %ebp
ret
_start:
movl $4, A
movl $5, B
call soma
movl %eax, %ebx
movl $1, $eax
int $0x80
Algoritmo 19: Traduo do programa do algoritmo 18.

2.2. IMPLEMENTAO DO MODELO EM UMA ARQUITETURA

39

No algoritmo 19, os endereos das variveis locais x e y so -4(%ebp) e -8(%ebp) respectivamente,


e o espao para estas variveis aberto logo no incio do procedimento ao subtrair 8 de %esp, quatro bytes
para x e quatro para y (lembre-se que a pilha cresce para baixo). O valor de %esp restaurado ao fim do
procedimento, e importante destacar que como ele foi alocado DEPOIS de %ebp ter sido empilhado, ele
liberado ANTES de restaurar %ebp.
importante destacar que todas as CPUs modernas (que eu conheo) tem registradores especficos para
lidar com o acesso de variveis em registros de ativao. As CPUs x86 chamam este registrador de base
pointer, enquanto que as CPUs MIPS chamam de frame register. Por vezes, o nome no to significativo
quanto estas duas CPUs.
Algumas CPUs chamam este registrador de frame pointer (fp), e outros usam nomes mais ou menos
significativos.

2.2.3 Com Parmetros e com Variveis Locais


Quando um procedimento tem parmetros, eles devem ser empilhados ANTES de chamar o procedimento
(antes do call), porm a ordem em que os parmetros devem ser empilhados pode variar de uma linguagem
para outra. Na linguagem Pascal, o padro que a ordem de empilhamento deve ser a mesma em que os parmetros aparecem, enquanto que na linguagem C, a ordem invertida, ou seja, o primeiro parmetro (aquele
que aparece primeiro aps o () o ltimo a ser empilhado, o segundo o penltimo e assim por diante. O
motivo para esta ordem (que no natural, diga-se de passagem) est relacionado com a implementao de
funes que tem um nmero varivel de argumentos, como por exemplo printf e scanf, onde o primeiro
argumento (o string) indica quantos parmetros foram (ou deveriam ter sido) empilhados.
Como estamos seguindo o padro utilizado na linguagem C, adotaremos o segundo modelo. Como
exemplo, considere o algoritmo 20, e sua verso assembly no algoritmo 21.

1
2
3
4
5
6
7
8
9
10
11
12
13
14

int a, b;
int soma ( int x, int y)
{
int z;
z = x + y;
return (z);
}
main ( int argc, char** argv)
{
a=4;
b=5;
b = soma(a, b);
return (b);
}
Algoritmo 20: Programa com parmetros e com variveis locais.

A diferena deste programa com relao aos anteriores que os parmetros foram empilhados antes
da instruo call linhas 20 e 21 na ordem inversa quela em que que aparecem. Os parmetros aparecem
acima das informaes gerenciais. Como 4(%ebp) corresponde ao endereo de retorno, os endereos dos
parmetros x e y so 8(%ebp) e 12(%ebp) respectivamente. O programa tambm apresenta uma varivel
global que est localizada em -4(%ebp).

2.2.4 Parmetros passados por Referncia e com Variveis Locais


Os parmetros da seo anterior foram passados por valor, ou seja, o valor foi copiado para a pilha. Isto faz
com que a varivel utilizada na chamada do procedimento no tenha o seu valor alterado quando do retorno.

40

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

CAPTULO 2. A SEO DA PILHA

.section .data
A: .int 0
B: .int 0
.section .text
.globl _start
_start:
soma:
pushl %ebp
movl %esp, %ebp
subl $4, %esp
movl 8(%ebp), %eax
addl 12(%ebp), %eax
movl %eax, -4(%ebp)
addl $4, %esp
popl %ebp
ret
_start:
movl $4, A
movl $5, B
pushl B
pushl A
call soma
addl $8, %esp
movl %eax, %ebx
movl $1, $eax
int $0x80
Algoritmo 21: Traduo do programa do algoritmo 20.

2.2. IMPLEMENTAO DO MODELO EM UMA ARQUITETURA

41

Porm, por vezes interessante que a varivel que corresponde ao parmetro seja alterada durante a
execuo da funo. Para satisfazer este tipo de necessidade, muitas linguagens de programao usam o
conceito de varivel passada por referncia. Na linguagem Pascal, um parmetro passado por referncia
precedido pela palavra reservada var.
A linguagem C, usada neste texto, no contm este tipo de construo, porm prov mecanismos para
que o programador crie uma construo anloga.
A idia bsica que o valor a ser empilhado na chamada no uma cpia do valor a ser passado,
porm uma cpia do endereo da varivel. Toda vez que a varivel for acessada, deve ser indicado o desejase acessar o valor apontado por aquele parmetro e no o parmetro em si. Como exemplo, considere o
algoritmo 23.

int a, b;
void troca ( int* x, int* y)
3 {
4
int z;
5
z = *x;
6
x = *y;
7
y = z;
8 }
9 main ( int argc, char** argv)
10 {
11
a=1;
12
b=2;
13
troca (&a, &b);
14
exit (0);
15 }
Algoritmo 22: Programa com parmetros passados por referncia e com variveis locais.
1

Neste algoritmo, a funo troca recebe dois parmetros, x e y e os troca. Ao final do programa
principal, a ter o valor 2 e b ter o valor 1. Alguns aspectos merecem destaque:
1. na chamada do procedimento troca (&a, &b) o smbolo & antes de uma varivel indica que
deve ser empilhado o endereo desta varivel e no o seu valor.
2. a funo void troca ( int* x, int* y) indica que os dois parmetros so apontadores,
ou seja, contm os endereos das variveis a serem acessadas. Por esta razo, o comando z = *x;
diz que o valor indicado pelo endereo contido em x deve ser copiado para z.
Dadas estas explicaes, fica mais simples indicar a traduo do programa acima (algoritmo 23).
O programa assembly reflete as idias apresentadas anteriormente:
1. A chamada da funo (troca (&a, &b)) empilha os endereos das variveis (linhas 25 e 26).
2. Os acessos aos parmetros so traduzidos da seguinte forma: A construo *<var>, corresponde a
duas instrues: movl <paramPilha>, %registrador1, movl (%registrador1), %registrador2. Como exemplo de traduo de *x, temos as instrues 15 e 16 do algoritmo 23.
Esperamos que a presente seo tenha esclarecido como e quando usar as construes &<var> e
*<var>, que so de uso misterioso para todos os programadores iniciantes na linguagem C.

2.2.5 A funo main da linguagem C


Cada linguagem de programao define um local para iniciar o programa. Na linguagem C, este local
o procedimento main. At este momento, associamos este procedimento ao rtulo start, que indica o

42

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

CAPTULO 2. A SEO DA PILHA

.section .data
A: .int 0
B: .int 0
.section .text
.globl _start
troca:
pushl %ebp
movl %esp, %ebp
subl $4, %esp
movl 8(%ebp), %eax
movl (%eax), %ebx
movl %ebx, -4(%ebp)
movl 12(%ebp), %eax
movl (%eax), %ebx
movl 8(%ebp), %eax
movl %ebx, (%eax)
movl -4(%ebp), %ebx
movl 12(%ebp), %eax
movl %ebx, (%eax)
addl $4, %esp
pop %ebp
ret
_start:
movl $1, A
movl $2, B
pushl $B
pushl $A
call troca
addl $8, %esp
movl $0, %ebx
movl $1, %eax
int $0x80
Algoritmo 23: Traduo do algoritmo 22.

2.2. IMPLEMENTAO DO MODELO EM UMA ARQUITETURA

43

local onde um programa assembly tem incio. Porm as coisas no ocorrem desta forma, e esta seo ir
apresentar alguns dos detalhes que envolvem a funo main da linguagem C.
Para comear, a funo main uma funo que tem dois argumentos: um inteiro (argc) e um vetor
de endereos (argv). O parmetro argc indica quantos argumentos foram includos na linha de comando
que chamou o programa. Como exemplo, considere o comando programa arg1 arg2 arg3. Quando
este programa entrar em execuo, argc ser empilhado com o valor 4 (pois so quatro palavras digitadas),
e os valores de argv sero os seguintes: argv[0]=programa, argv[1]=arg1, argv[2]=arg2,
argv[3]=arg3.
Conforme o modelo de execuo, estes parmetros esto na pilha e podem ser acessados a partir de
%ebp, e assim o . Porm, isto leva a duas outras perguntas:
P Quem foi que colocou estes valores na pilha?
R o sistema operacional, mais especificamente o carregador de programas. Ele quem organiza o espao
virtual de execuo de programas, e entre vrias outras tarefas, coloca os argumentos da linha de
comando na pilha antes de liberar o programa para execuo. importante destacar que em linux, o
carregador faz isto para TODOS os programas, independente da linguagem em que foi desenvolvido
(a linguagem C tem o mecanismo descrito acima para acessar estas informaes, e outras linguagens
tem outros mecanismos).
P Onde ficam e como so organizados estes parmetros?
R Ficam na pilha, e para acessar argc a partir de um programa assembly basta usar 8(%ebp), como fizemos
em todos os demais procedimentos
Para exemplificar, considere o algoritmo 24. Vamos execut-lo no simulador ald, onde podemos analisar
cuidadosamente alguns aspectos de sua execuo.

1
2
3
4
5
6
7
8
9
10

.section .text
.globl _start
pushl %ebp
movl %esp, %ebp
movl 4(%ebp), %eax
movl 8(%ebp), %ebx
movl 12(%ebp), %ebx
movl 16(%ebp), %ebx
movl $1, %eax
int $0x80
Algoritmo 24: Programa que acessa os parmetros da funo main.

O programa basicamente armazena o valor de argc em %eax e depois armazena os parmetros argv[0],
argv[1] e depois argv[2] em %ebx.
Porm, este procedimento especial, pois no tem de endereo de retorno. Afinal, quem coloca
parmetros e o endereo de retorno na pilha o procediemento chamador. Como no h procedimento
chamador, somente foram empilhados os parmetros, e por esta razo, o seu registro de ativao diferente de
todos os outros. No lugar onde estaria o endereo de retorno, est argc, onde estaria argc est argv[1],
e assim por diante.
No procedimento que corresponde ao _start e somente nele, os endereos dos parmetros so os
seguintes:

CAPTULO 2. A SEO DA PILHA

44

parmetro
endereo lxico
argc
4(%ebp))
argv[0]
8(%ebp))
argv[1]
12(%ebp))
argv[2]
16(%ebp))
argv[3]
20(%ebp))
...
...
Considere que o programa do algoritmo 24 chama-se imprArgs, e queremos execut-lo da seguinte
forma:
> ./imprimeArgs XXX YYY ZZZ
Porm ao invs de execut-lo na linha de comando, fazemos isso no simulador ald. Para colocar argumentos no programa, aps entrar no simulador, digite set args XXX YY ZZZ e load <nome do programa>. Ao longo da execuo, verifique os valores que so colocados em %ebx e examine aquelas posies de memria (comando examine). Em minha mquina, os valores so, na ordem: 0xBFFFF976,
0xBFFFF97F e 0xBFFFF983. Estes endereos podem variar de mquina para mquina. Quando executei
o programa no simulador, e examinei o contedo de argv[0], obtive o seguinte resultado:
ald> examine 0xBFFFF976
Dumping 64 bytes of memory starting at 0xBFFFF976 in hex
BFFFF976: 69 6D 70 72 41 72 67 73 00 58 58 58 00 59 59 59
BFFFF986: 00 5A 5A 5A 00 4D 41 4E 50 41 54 48 3D 3A 2F 75

imprArgs.XXX.YYY
.ZZZ.MANPATH=:/u

O endereo inicial a ser impresso 0xBFFFF976 (argv[0]). Este endereo contm o primeiro byte
do nome do meu programa (imprArgs) seguido de um byte zero, que indica fim desta cadeia de caracteres.
O endereo seguinte 0xBFFFF97F(argv[1]), que corresponde ao primeiro byte do primeiro argumento
(XXX), e assim por diante. Observe que todas as cadeias de caracteres indicadas por argv terminam em
zero.
Um fato curioso no dump de memria acima o que vem depois do ltimo argumento do programa:
MANPATH=. . .. Esta uma varivel de ambiente, e todas as variveis de ambiente so listadas em seqncia
aps o ltimo parmetro (por exemplo, HOME, PWD, etc.. Com este mecanismo, qualquer programa, quando
em execuo, contm uma fotografia do valor das variveis de ambiente tinham quando ele foi executado.
Para encontrar o valor de uma varivel de ambiente, basta procurar pelo nome dela a partir fim da lista de
argumentos (neste caso, ZZZ). Os programadores C podem obter o valor destas variveis de ambiente atravs
da funo getenv.

2.2.6 Chamadas Recursivas


Para finalizar o estudo sobre o funcionamento de chamadas de procedimento, esta seo apresenta um exemplo de um programa recursivo. A idia fixar todos os conceitos apresentados sobre chamadas de procedimento, em especial a forma com que os registros de ativao so empilhados para um mesmo procedimento
(ou seja, como a implementao de vrias folhas de papel sobrepostas que correspondem a um mesmo
procedimento).
O algoritmo 25 contm todos estes elementos. Ele calcula o fatorial de um nmero (no caso, de 4).
Existem maneiras muito mais elegantes e muito mais eficientes para implementar um programa que calcula
o fatorial, porm este programa foi implementado desta maneira (at certo ponto confusa) para incluir parmetros passados por referncia, parmetros passados por valor e variveis locais (observe que a varivel r
desnecessria).
A traduo do algoritmo 25 apresentada no algoritmo 26.
Os aspectos importantes a serem destacados neste algoritmo so os seguintes:
a forma de inserir o endereo de x na pilha (linhas 44 a 46).
a forma de acessar o valor de x (atravs do endereo que est na pilha (*x): linhas 27 e 28.
os passos para a chamada recursiva (linhas 21 a 25).

2.2. IMPLEMENTAO DO MODELO EM UMA ARQUITETURA

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

void fat ( int* res, int n)


{
int r;
if (n<=1)
*res=1;
else {
fat (res, n-1);
r = *res * n;
*res=r;
}
}
main (int argc, char** argv)
{
int x;
fat (&x, 4);
return (x);
}
Algoritmo 25: Programa Recursivo.

Figura 2.3: Registro de ativao com indicao dos campos.

45

46

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46

CAPTULO 2. A SEO DA PILHA


.section .data
.section .text
.globl _start
fat:
pushl %ebp
movl %esp, %ebp
subl $4, %esp
movl 12(%ebp), %eax
movl $1, %ebx
cmpl %eax, %ebx
jl else
movl 8(%ebp), %eax
movl $1, (%eax)
jmp fim_if
else:
movl 12(%ebp), %eax
subl $1, %eax
pushl %eax
pushl 8(%ebp)
call fat
addl $8, %esp
movl 8(%ebp), %eax
movl (%eax), %eax
movl 12(%ebp), %ebx
imul %ebx, %eax
movl %eax, -4(%ebp)
movl -4(%ebp), %eax
movl 8(%ebp), %ebx
movl %eax, (%ebx)
fim_if:
addl $4, %esp
popl %ebp
ret
_start:
pushl %ebp
movl %esp, %ebp
subl $4, %esp
pushl $4
movl %ebp, %eax
subl $4, %eax
pushl %eax
call fat
addl $8, %esp
movl -4(%ebp), %ebx
movl $1, %eax
int $0x80
Algoritmo 26: Traduo do Algoritmo 25

2.2. IMPLEMENTAO DO MODELO EM UMA ARQUITETURA

47

A figura 2.3 detalha o contedo de um registro de ativao. Observe que %ebp aponta para o endereo
de memria que contm o valor de %ebp do registro de ativao anterior. Nesta figura, fica mais fcil de
perceber qual o endereo relativo a %ebp dos parmetros e das variveis locais.
A figura 2.4 mostra uma srie de registros de ativao empilhados, como seria na execuo do programa
recursivo tratado nesta seo. Cada registro de ativao est representado com uma cor diferente. Da esquerda para a direita, a figura mostra como os registros de ativao so colocados na pilha a cada chamada
de procedimento. A configurao mais equerda obtida no procedimento main, a configurao direita
corresponde chamada de fat(4), e esquerda desta, correspode a fat(3), e assim por diante at fat(1).
O objetivo desta figura ressaltar os valores de %ebp ao longo do tempo. Ele sempre aponta para o
registro de ativao corrente, no endereo exato onde est guardado o registro de ativao do procedimento
anterior a este. As setas indicam a lista encadeada de valores de %ebp.
A figura mostra que o registrador %ebp aponta para ltimo registro de ativao colocado na pilha. O
endereo apontado por ele o local onde o valor anterior de %ebp foi salvo (veja figura 2.3). O valor antigo
de %ebp aponta para outro registro de ativao (imediatamente acima). Este, por sua vez, aponta para o
registro de ativao anterior, e assim por diante. O ltimo registro de ativao corresponde ao procedimento
main conforme destacado na figura.

Figura 2.4: Registros de ativao do procedimento fat ao longo das chamadas recursivas.
Como exerccio, indique:
1. quais instrues assembly so responsveis pelo empilhamento e pelo desempilhamento de cada
campo do registro de ativao.

CAPTULO 2. A SEO DA PILHA

48

2. preencha os valores das variveis do programa em C nos campos relacionados com as variveis na
figura 2.4.

2.2.7 Uso de Bibliotecas


Os programas apresentados at o momento ou no imprime os resultados, ou os coloca em uma varivel de
ambiente. Agora descreveremos como trabalhar com funes desenvolvidas externamente, mais especificamente com as funes disponveis na libc, em especial as funes printf e scanf.
Para utilizar estas duas funes, so necessrios dois cuidados:
1. Dentro do programa assembly, empilhar os procedimentos como descrito neste captulo.
2. Ao ligar o programa, necessrio incluir a prpria libc. Como faremos a ligao dinmica, necessrio incluir a biblioteca que contm o ligador dinmico.
Para exemplificar o processo, considere o algoritmo 27, e sua traduo, o programa28.

1
2
3
4
5
6

main (int argc, char** argv)


{
int x, y;
scanf ("Digite dois numeros %d %d ", &x, &y);
printf("Os numeros digitados foram %d %d \n ", x, y);
}
Algoritmo 27: Uso de printf e scanf.

Como pode ser observado, a traduo literal. Empilha-se os parmetros e em seguida h a chamada
para as funes printf e scanf.
>
>
>
>
>

as psca.s -o psca.o
ld psca.o -o psca -lc -dynamic-linker /lib/ld-linux.so.2
./psca
Digite dois nmeros: 1 2
Os numeros digitados foram 1 2

As funes printf e scanf no esto implementadas aqui, e sim na biblioteca libc, disponvel em
/usr/lib/libc.a (verso esttica) e /usr/lib/libc.so (verso dinmica). Como mais conveniente utilizar a biblioteca dinmica, usaremos esta para gerar o programa executvel.
A opo -dynamic-linker /lib/ld-linux.so.2 indica qual o ligador dinmico que ser o
responsvel por carregar a biblioteca libc, indicada em -lc, em tempo de execuo.

2.3 Aspetos de Segurana


A pilha j foi alvo de ataques, e foi considerada um ponto vulnervel em sistemas Unix (e conseqentemente
linux). Esta falha de vulnerabilidade era conseqncia da implementao de famlia de funes getc,
getchar, fgetc, que no verificam violaes do espao alocado para as variveis destino.
Como exemplo, veja o algoritmo 29.
A primeira coisa interessante que ao compilar este programa, surge uma mensagem curiosa:
> gcc get.c
/tmp/ccaXi9xy.o: In function main:
get.c:(.text+0x1f): warning: the gets function is dangerous and should not be used.
O aviso que a funo gets perigosa e que no deve ser usada. O porqu deste perigo que ela no
verifica os limites do vetor (que no caso tem cinco bytes). Para demonstrar o problema, veja o que ocorre
com a execuo onde a entrada de dados maior do que o tamanho do vetor:

2.3. ASPETOS DE SEGURANA

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

1
2
3
4

.section .data
str1: .string "Digite dois nmeros: "
str2: .string "%d %d"
str3: .string "Os numeros digitados foram %d %d \n"
.section .text
.globl _start
_start:
pushl %ebp
movl %esp, %ebp
subl $8, %esp
pushl $str1
call printf
addl $4, %esp
movl %ebp, %eax
subl $8, %eax
pushl %eax
movl %ebp, %eax
subl $4, %eax
pushl %eax
pushl $str2
call scanf
addl $12, %esp
pushl -8(%ebp)
pushl -4(%ebp)
pushl $str3
call printf
addl $12, %esp
movl $1, %eax
int $0x80
Algoritmo 28: Traduo do Algoritmo 27

main ( int argc, char** argv);


{
char s[5];
gets (s); printf("}
Algoritmo 29: O perigoso gets

49

50

CAPTULO 2. A SEO DA PILHA

> ./get
1234567890
1234567890
Observe que foram digitados 10 caracteres, e eles foram armazenados (com sucesso) no vetor. Evidentemente eles no cabem no espao alocado para eles, e podem sobreescrever outras variveis.
Estas variveis so alocadas na pilha, e o primeiro artigo que citou esta vulnerabilidade foi no artigo
postado na internet Smashing The Stack For Fun And Profit de Aleph One.
Este ataque ocorre quando o usurio entra com uma quantidade de dados muito maior do que o tamanho
da varivel, sobreescrevendo outros dados na pilha, como o endereo de retorno do registro de ativao
corrente.
Se os dados digitados inclurem um trecho de cdigo (por exemplo, o cdigo de /usr/bin/sh) ser injetado
um trecho de cdigo na pilha. O prximo passo envolve o comando assembly ret. Se antes desta instruo
contiver o endereo do cdigo injetado, ento o cdigo injetado ser executado.
Agora, vamos analisar o que ocorreria na injeo em um processo que executa como super-usurio,
como por exemplo, o ftp em linha de comando.
Se o cdigo injetado for o sh, o usurio no s executar a shell, mas a executar como super-usurio!
Mais do que simples conjecturas, este mtodo foi usado (e com sucesso) durante algum tempo. Para
mais detalhes, veja [JDD+ 04].
Desde a publicao do artigo e da deteco da falha de segurana, foram criados mecanismos para evitar
este tipo de invaso, pode ser visto na execuo do algoritmo 29, com maior quantidade de dados.
./get
10101010101010101010101010110
*** stack smashing detected ***: ./get terminated
======= Backtrace: =========
...

Captulo 3

Chamadas de Sistema
No modelo ELF, um programa em execuo no pode acessar nenhum recurso externo sua memria virtual.
Alis, existem regies dentro de sua prpria rea virtual que o programa tambm no pode acessar, como
pde ser visto na figura 1.
Porm, praticamente todos os processo precisam acessar dados que esto fora de sua rea virtual, como
arquivos, eventos do mouse e teclados, entre vrias outras.
No ELF, o mecanismo que permite acessar estes recursos utiliza chamadas ao sistema (system calls).
Este captulo descreve o que so e como funcionam as chamadas ao sistema no linux em especial em
mquinas x86. Como o conceito de chamadas ao sistema ser universal, tambm apresentamos o modelo que
foi adotado no MS-DOS. O objetivo mostrar que as chamadas ao sistema, se mal utilizadas, podem causar
srios problemas ao computador.
Explicar o que so e como funcionam as chamadas ao sistema na prtica envolve conhecimentos maiores
de hardware, e para manter uma abordagem mais leve, apresentamos algumas analogias.
A primeira analogia descrita na seo 3.1, e explica os modos de execuo de um processo, usurio e
supervisor. Em seguida, a seo 3.2 acrescenta CPU e a seo 3.3 acrescenta os perifricos (dispositivos de
hardware) analogia.
Com a analogia completa, a seo3.4 explica como as coisas ocorrem na prtica em uma arquitetura.
Em seguida, a seo 3.6 apresenta uma outra forma de implementar as chamadas ao sistema (MS-DOS), e
relata alguns problemas que podem ser gerados com esta implementao. Por fim, a seo 3.7 apresenta as
chamadas ao sistema no linux.

3.1 A analogia do parquinho


Uma analogia que eu gosto de usar a de que um processo em execuo em memria virtual semelhante a
uma criana dentro de uma caixa de areia em um parquinho de uma creche.
Imagine uma creche onde as crianas so colocadas em reas delimitadas para brincar, uma criana por
rea. Normalmente isto acontece em creches, onde estas reas so caixas de areia.
Porm nesta analogia, ao contrrio do que ocorre me creches, cada criana confinada a uma nica rea,
no podendo sair dali para brincar com outras crianas.
Agora, considere uma criana brincando em uma destas reas. O que ela tem para brincar aquilo que
existe naquela caixa de areia. Ali, ela pode fazer o que quiser: jogar areia para cima, colocar areia na boca
(e outras coisas que prefiro no descrever).
Tudo o que a criana fizer naquela caixa ter conseqncias unicamente para ela, ou seja, no afeta as
crianas em outras caixas de areia.
Normalmente, s h uma criana por caixa de areia. Quando a criana terminar de brincar, a caixa de
areia destruda. Para cada criana que quiser brincar mas no estiver em uma caixa de areia, uma nova
caixa ser construda para ela, possivelmente usando areia de caixas destrudas anteriormente.

51

CAPTULO 3. CHAMADAS DE SISTEMA

52

Porm, importante garantir que as lembranas deixadas por uma criana no sejam passadas para
outras crianas. Uma forma de fazer isso, esterilizando todo o material das caixas de areia destrudas, como
por exemplo esterilizando a areia utilizada.
Quem faz esta tarefa a tia, que aqui conhecida como supervisora. Alis, bom destacar que
isto que se espera de uma creche bem conceituada: que cuide da sade das crianas. Voc deixaria seu filho
em um parquinho onde a supervisora negligencia a sade das crianas?
Esta supervisora tambm responsvel por outras tarefas, como atender s requisies das crianas por
dispositivos externos.
Suponha que existem brinquedos para as crianas usarem nas caixas de areia: baldinho, regador, etc..
Como as crianas no podem sair de sua caixa de areia, elas tem de pedir o brinquedo para a supervisora.
A supervisora recebe o pedido, e procura por algum brinquedo disponvel. Eventualmente ir encontrar
(nem que seja tirando outra caixa de areia onde est outra criana), porm antes de passar para a criana que
solicitou, deve primeiramente higienizar o brinquedo para que lembranas deixadas pela primeira criana
no possam ser usadas pela segunda criana.
Nesta analogia, temos que:
crianas e supervisora so os processos;
crianas tem uma rea restrita de ao, enquanto que a supervisora tem uma rea de atuao quase
total;
cada caixa de areia a memria virtual de um processo.
os brinquedos so recursos, como por exemplo dados de arquivo, de teclado, de mouse, etc.. Ou seja:
so os objetos que um processo no pode acessar porque esto fora de sua memria virtual;
os pedidos e devolues de recursos externos so executados atravs das chamadas ao sistema (system
calls).
Um detalhe importante que crianas e supervisores, apesar de serem processos atuam em modos
diferentes. Enquanto uma criana s pode acessar a sua caixa de areia e as coisas que estiverem l dentro, a
supervisora pode acessar qualquer caixa de areia, os espaos entre as caixas de areia e at fora delas (como
por exemplo, em sua sala particular). Estes dois modos so conhecidos como modo protegido (para as
crianas) e modo supervisor (para a supervisora).
Assim, as chamadas ao sistema so basicamente requisies que o processos fazem ao sistema operacional por recursos externos sua rea virtual. Para acessar qualquer coisa que estiver fora de sua rea virtual,
o processo deve fazer uma chamada ao sistema.

3.2 Acrescentando a CPU


Na analogia da seo anterior, todos os processos (crianas e supervisora) podem trabalhar em paralelo,
ou seja, duas crianas podem brincar em suas caixas de areia simultaneamente.
Porm, em uma arquitetura real, somente os processo que esto usando a CPU que esto ativos. Os
demais esto em espera.
Para explicar isso na analogia do parquinho, vamos introduzir um pouco de magia.
Uma moradora vizinha no gosta do barulho que as crianas fazem quando esto brincando. Para azar
de todos, ela uma bruxa malvada que invocou uma maldio terrvel para silenciar todos.
Esta maldio ilumina todo o parquinho com uma luz cinza que tranforma todos os atingidos em esttuas
(inclusive a supervisora).
Outra vizinha uma bruxa boazinha, que estranhando a falta de barulho, percebeu o que ocorreu. Ela
no uma bruxa to poderosa quanto a bruxa malvada, e no pde reverter o feitio.
Mesmo assim, conseguiu conjurar uma fadinha voadora, imune luz cinza. Quando ela sobrevoa uma
criana (ou a supervisora) consegue despert-la por algum tempo usando uma luz colorida. Infelizmente,
a fadinha s consegue acordar exatamente uma pessoa por vez.
Por esta razo, a bruxa boazinha pediu para a fadinha no ficar muito tempo em uma nica pessoa, e
mudar de pessoa a pessoa para que todos pudessem brincar um pouco.
Porm, a bruxa boazinha observou que a fadinha no pensava muito para escolher a prxima pessoa
para acordar, e com isso ela acabava acordando somente um grupo pequeno de pessoas (e outras no eram
sequer acordadas).

3.3. ACRESENTANDO PERIFRICOS E DISPOSITIVOS DE HARDWARE

53

Para encontrar uma alternativa mais justa, a bruxa boazinha pediu para que a fadinha sempre acordasse
a supervisora aps uma criana. A supervisora foi informada da situao, e foi incumbida de encontrar uma
forma mais justa para encontrar a prxima criana para acordar.
Toda vez que a fadinha acordava a supervisora, ela anotava qual a ltima criana acordada e escolhia a
prxima a ser acordada de acordo com as suas anotaes. Em seguida, a fadinha ia acordar a criana indicada
pela supervisora.
Um outro problema que a fadinha no tinha relgio. Com isso, por vezes ficava tempo demais sobre
uma criana antes de voltar supervisora, causando injustias.
Para resolver este ltimo problema, a bruxa boazinha deu um pager fadinha, e instalou um relgio que
ativa o pager em intervalos regulares. Estes intervalos so chamados de quanta, e correspondem ao tempo
mximo que a fadinha fica sobre uma criana.
Cada vez que o relgio ativa o pager, a fadinha acorda a supervisora e mostra o pager para ela. L
est indicado quem que o ativou (no caso, o relgio). Com esta informao, a supervisora sabe que deve
executar uma tarefa especfica: escolher qual a prxima criana ser acordada. Quando fizer a escolha, a
fadinha vai at a criana indicada e a ilumina at que ocorra outra ativao do pager.
Imagine agora que o intervalo do relgio de um minuto. Quando se passar uma hora, at 60 crianas
tero brincado. Deste ponto de vista, todas as crianas parecem ter brincado em paralelo. Na prtica, isto
ocorre em um computador. O intervalo de execuo de cada processo to pequeno, que em um segundo,
dezenas de processos entram e saem de execuo, dando a iluso de paralelismo. A esta iluso se d o nome
de pseudo-paralelismo.
A fadinha que foi apresentada aqui corresponde CPU. A alternncia de processos (process switch)
ocorre da forma sugerida aqui. Cada vez que o relgio bate, a CPU consulta o sistema operacional para que
ele indique qual o prximo processo a ser acordado. Existem vrios algoritmos para escolher qual o prximo
processo a ser acordado, como por exemplo escolher o processo que est h mais tempo sem utilizar a CPU.

3.3 Acresentando perifricos e dispositivos de hardware


Uma outra entidade que ser estendida aquela que fornece os brinquedos. Imagine que do lado de fora
do parquinho existem vrios quiosques especializados em fornecer brinquedos. Um quiosque s guarda
baldinhos, outro s regadores, e assim por diante. Quando uma criana chama a supervisora, ela pode
especificar qual o brinquedo que ela quer. Por exemplo, quero o baldinho azul turquesa, nmero 0xab00
(sim, as crianas sabem hexadecimal). A supervisora anota o nmero envia uma mensagem para o quisque
de baldinhos solicitando o baldinho correspondente. Em sua caderneta, a atendente tambm anota qual foi a
criana que pediu por aquele baldinho.
O atendente de cada quiosque meio lento, pois so dezenas de milhares de brinquedos em cada
quiosque (no me perguntem como eles cabem l). Cada quiosque tem uma fadinha prpria (ou seja, a
fadinha do parquinho no precisa ir at l para dar vida ao atendente pois ele nunca vira esttua).
Se a supervisora ficar esperando pelo baldinho, ela no poder atender s demais requisies das crianas, causando grande consternao audvel (mesmo sendo de uma s criana por vez). Por esta razo,
ela volta a atender as requisies do parquinho e espera que o atendente avise quando encontrar o que foi
solicitado.
Quando o atendente encontrar, ele precisa avisar supervisora que encontrou o que foi pedido. Para este
tipo de situao, a fadinha foi equipada com um dispositivo que pode ser acionado pelo pelos atendentes dos
quiosques: um pager. Ao perceber que o pager foi acionado, a fadinha vai at a supervisora e a acorda. No
pager tem o nmero do quiosque que enviou a mensagem. A supervisora procura em sua caderneta quem
pediu aquele dispositivo, e o coloca na caixa de areia correspondente.
Como o mecanismo de pegar brinquedos pode ser diferente dependendo do quiosque (por exemplo, para
pegar um baldinho usa-se um mecanismo diferente do que para pegar areia, ou gua), o nmero do quiosque
j suficiente para que a supervisora saiba como pegar aquele brinquedo. O atendente nunca sai do quiosque,
e por isso, um mensageiro dedicado quela tarefa usado para transportar o baldinho. Para cada quiosque
h um mensageiro especializado.
Nesta verso estendida da analogia, temos que:
as fadas corresponem CPU. Existem tantas fadas para dar vida ao parquinho quantas CPUs em uma
mquina.
os quiosques so os dispositivos perifricos (disco, teclado, mouse, etc.). Quase todos tem uma CPU
dedicada a executar o que foi requisitado pela supervisora. Porm, eles no tem acesso ao parquinho.

CAPTULO 3. CHAMADAS DE SISTEMA

54

quando a requisio ao quiosque fica disponvel, o atendente ativa o pager, que em termos tcnicos
chamados interrupo. Nos x86, a CPU sabe que houve uma interrupo quando o pino INTR)
ativado.
a supervisora um processo. Ela executa cdigo, e tem subrotinas dedicadas a tratar as requisies
dos dispositivos. Cada dispositivo pode ser tratado por um trecho de cdigo diferente, e uma forma
de acessar o trecho de cdigo correto atravs do nmero que aparece no pager. Aps perceber que
o pino INTR foi ativado, a CPU pergunta quem mandou a interrupo, e o dispositivo responde
com o seu nmero.

3.4 Interrupes
A analogia da seo anterior bastante fiel ao que realmente ocorre em um computador para que os processos conversem com os dispositivos. Esta conversa ocorre atravs das chamadas de sistema, e esta seo
descreve o que realmente ocorre dentro de uma arquitetura. Cada arquitetura trata estes eventos de maneiras
diferentes, e aqui apresentaremos como funciona o modelo de tratamento das arquiteturas da famlia x86.
A conversa entre um processo e os dispositivos externos envolve dois passos. O primeiro ocorre
quando o processo solicita dados ao dispositivo, e o segundo ocorre quando o dispositivo coloca os dados na
rea virtual do processo.
Por uma questo didtica, descreveremos inicialmente o segundo passo e depois o primeiro.
A seo 3.4.1 explica como trazer os dados de um dispositivo externo (um quiosque) para dentro da rea
virtual de endereamento de um processo (a caixa de areia). Esta ao executada utilizando um mecanismo
chamado de interrupo de hardware.
Por fim, a seo 3.4.2 descreve as interrupes de sofware e uma delas em especial, aquela que executa
as chamadas ao sistema.

3.4.1 Interrupo de Hardware


Considere que um dispositivo de hardware terminou uma tarefa. Por exemplo, que o disco acabou de ler o
bloco de dados que lhe foi solicitado. Neste ponto, o dispositivo (no caso, disco) envia um sinal para a CPU.
Nos computadores da famlia x86, este sinal ativa um pino especfico chamado INTR. A este evento d-se
o nome de interrupo.
Quando este pino ativado, a CPU termina a instruo que est executando, e suspende a execuo para
tratar a interrupo. Porm, como todos os dispositivos esto ligados no mesmo pino, a CPU ainda no sabe
qual foi o dispositivo que ativou a interrupo.
Para descobrir, a CPU pergunta quem foi que disparou a interrupo, e o dispositivo que o fez envia o
seu nmero para a CPU (cada dispositivo tem um nmero diferente).
Agora, a CPU j sabe quem ativou o pino, porm no h uma forma padronizada para buscar os dados
que esto l. Cada dispositivo tem uma forma diferente de ser acessado (alguns tem at uma linguagem
prpria). Para recuperar os dados, necessrio usar a forma correta (ou linguagem correta). No sistema
operacional, h um trecho de cdigo para tratar cada interrupo de hardware. Este trecho chamado de
driver do dispositivo.
Como existem vrios drivers, a CPU precisa ativar o driver correto. Para tal, ela usa o nmero do
dispositivo como ndice em um vetor. Este vetor contm os endereos dos trechos de cdigo de cada driver.
Este mecanismo parece complicado primeira vista, e para deix-lo mais claro, vamos dividir sua descrio
em duas fases:
1. Colocar os endereos dos drivers no vetor. Este processo chamado de registro de driver.
2. Usar o vetor para disparar o driver correto;

3.4.1.1 Registrando o driver


O algoritmo 30 mostra como armazenar os endereos de cada driver no vetor. Isto feito durante o carregamento do sistema operacional (boot) para a memria, quando as rotinas de tratamento de interrupo j
esto alocadas em algum endereo.
Neste exemplo, o driver0 corresponde ao trecho de cdigo do sistema operacional que faz o tratamento de interrupo do dispositivo de nmero zero.

3.4. INTERRUPES

55

Na prtica, nem todos os drivers precisam ser carregados, uma vez que nem todos os dispositivos esto
presentes em algumas mquinas.

1
2
3
4
5
6
7
8
9
10
11
12

...
driver0 () { . . . }
driver1 () { . . . }
...
driverN () { . . . }
...
alocaDriverEmVetor () {
vetorInterr[0] = &driver0;
vetorInterr[1] = &driver1;
...
vetorInterr[N] = &driverN;
}
Algoritmo 30: Carregamento dos endereos dos drivers no vetor.

3.4.1.2 Disparando o driver correto


O algoritmo 31 mostra esquematicamente uma rotina de encaminhamento de interrupo de hardware. O
seu parmetro o nmero do dispositivo.
A varivel driverCerto uma varivel que contm o endereo de uma funo. Basta copiar o
endereo contido no vetor de Interrupo para esta varivel e dispar-la para que o driver apropriado seja
executado.

1
2
3
4
5
6
7
8

...
void TrataInterrupHardw (int numeroDispositivo) {
void* (*driverCerto)();
...
driverCerto = driverInterr[numeroDispositivo];
driverCerto ();
...
}
Algoritmo 31: Direcionamento para o driver correto.
O algoritmo 31 um modelo conceitual. Na famlia x86, este algoritmo diferente em alguns aspectos.
o vetor de interrupes no uma varivel do sistema operacional, e sim um conjunto de endereos
fixos na memria. Nos processadores da famlia x86, o incio deste vetor configurvel [aPAW93]
porm como referncia, considere que comea no endereo 0x0 (zero absoluto da memria).
quem dispara o driver no o sistema operacional, mas sim a prpria CPU. No contexto de uma
interrupo, ao receber o nmero do dispositivo, a CPU usa este nmero como ndice do vetor de
interrupo, e coloca o endereo l contido no %eip.

Em linux, o elenco de interrupes armazenado no arquivo /proc/interrupts, e pode ser visualizado


da seguinte forma:
> cat /proc/interrupts

CAPTULO 3. CHAMADAS DE SISTEMA

56

A explicao do resultado obtido foge ao escopo deste livro, mas pode ser facilmente encontrada atravs
de buscadores da internet.
Uma questo final: nem todas as entradas do vetor de interrupo so preenchidas. Considere que temos
10 dispositivos de hardware. Eles no precisam ser mapeados entre os ndices [0..9], podendo ser mapeados,
por exemplo, entre os ndices [0..19]. Isto implica dizer que podem existir buracos no vetor de interrupo.
Aqui vai uma figura mostrando os drivers na memria e cada vetor de interrupo
apontando para eles

3.4.1.3 Exemplo
As interrues de hardware, como o prprio nome j diz, so para atender s requisies dos dispositivos de
hardware. Alguns deles so de utilidade bvia como disco, mouse, teclado, vdeo, entre outros.
Porm existem alguns cuja utilidade no aparente. Um destes dispositivos o timer. Este dispositivo
gera um impulso em intervalos regulares (como o relgio da seo 3.2).
Este dispositivo um oscilador de cristal que gera interrupes em intervalos regulares, e tratado como
interrupo de hardware1
A interrupo que este dispositivo gera no usada para transferir dados, mas sim para acordar o
sistema operacional, que ento pode realizar tarefas administrativas, como por exemplo, trocar o processo
em execuo por outro processo.
por isso que quando a fadinha da seo 3.2 ouve o barulho do relgio, ela ilumina o supervisor para
que ele indique uma nova criana para a fada iluminar.
Normalmente, quando o timer dispara o sistema operacional, a primeira tarefa do S.O. desabilitar
as interrupes, para que ele no possa ser incomodado durante sua execuo. Por esta razo, bastante
importante que o S.O. no entre em loop infinito.

3.4.2 Interrupo de Sofware


A seo anterior mostrou que o vetor de interrupes alimentado com os nmeros dos dispositivos. Porm,
tambm possvel colocar endereos de drivers que no esto associados com dispositivos, mas sim com
eventos de software.
Os drivers contidos nestes ndices evidentemente no sero ativados por eventos de hardware, mas sim
atravs do que se chama de interrupes de software ou por armadilhas2 . O primeiro termo normalmente
usado para indicar eventos previstos pelo programa, como as chamadas ao sistema. O segundo termo
usado para eventos causados por condies excepcionais como diviso por zero, pgina invlida, etc..
Nos x86, as as interrupes de software so ativadas pelo comando assembly INT. Este comando recebe
um parmetro, que o ndice do vetor de interrupo que deve ser ativado.
Desta forma, a instruo int $0x80 nada mais do que a solicitao para execuo do driver cujo
endereo est contido no ndice 0x80 do vetor de interrupo.
Assim como nas interrupes de hardware, quem alimenta o vetor interrupo com os endereos das
rotinas de tratamento das interrupes e software o sistema operacional durante o boot.
No linux, estas rotinas so parte integrante do sistema operacional, ou seja, int $0x80 uma forma
de disparar a execuo de um trecho de cdigo do sistema operacional.
Como um processo regular no pode acessar a rea do sistema operacioanal (e muito menos o vetor de
interrupo), a instruo INT primeiramente muda o modo de execuo de modo protegido para modo
supervisor e em seguida, desvia o fluxo para o endereo contido no ndice 0x80 do vetor de interrupo.
Um programa no modo supervisor tem acesso a todos os recursos do hardware, e pode acessar qualquer posio de memria ou perifricos.
Quando a requisio for atendida (ou enviada para o dispositivo apropriado), o sistema operacional
executa a instruo IRET para retornar para o programa que solicitou a chamada ao sistema, em modo
protegido.
1 Procure
2 trap

a posio deste evento no vetor de interrupes no arquivo /proc/interrupts.

3.5. OS DRIVERS NATIVOS

57

3.5 Os drivers nativos


Tudo o que foi explicado at aqui parte do princpio que o sistema operacional j foi carregado, em especial
os drivers dos dispositivos.
Quando se liga o boto para ligar um computador, uma das primeiras tarefas executadas buscar pelo
sistema operacional nos dispositivos do hardware como por exemplo no pendrive, disquete, cd, disco rgido.
Porm, se os drivers so parte integrante do sistema operacional (ainda no carregado), como possvel
acessar os dispositivos de hardware?
A resposta est drivers pr-instalados. Estes drivers esto armazenados em memria ROM3 , ou seja,
estes drivers so parte da mquina. Sem eles, no seria possvel acessar os dispositivos de hardware.
Abaixo, descrevemos em linhas gerais o que ocorre quando um computador ligado. Uma descrio
mais detalhada foge ao escopo deste livro e pode ser encontrada em livros como [aPAW93] ou em vrios
stios na internet.
Assim que o computador iniciado, um trecho de cdigo assembly contido nesta memria ROM
executado. Este trecho de cdigo faz uma srie de verificaes e analisa quais os dispositivos disponveis no
computador. Em seguida, o usurio pode indicar qual o dispositivo que ele quer que seja usado para iniciar
o computador (o SETUP).
Ao saber qual o dispositivo que deve ser usado, o programa examina a MBR4 do dispositivo. No disco
rgido de um PC, a MBR corresponde trilha zero.
O MBR no tem grande capacidade, por volta de 512 bytes, e contm dois tipos de informao:
Master Partition Table informaes sobre as parties contidas no dispositivo.
Master Boot Code um pequeno trecho de cdigo que ser usado para carregar o sistema operacional.
Nosso interesse o Master Boot Code. Este trecho carregado para a memria fsica e aps carregado,
o controle (%eip) passado para ele.
Ele usa os drivers da BIOS para acessar o dispositivo e carregar o sistema operacional para a memria.
Quando ele termina, o controle passado ao sistema operacional.
s nesta hora que os drivers do sistema operacional so carregados para a memria, e os endereos do
vetor de interrupo so ajudados.
Os drivers da BIOS so genricos, e funcionam para vrios dispositivos. Porm, alguns dispositivos
requerem drivers especficos que so capazes de extrair melhor eficincia deles. o caso de drivers de
vdeo, que atendem desde requisies simples (como iluminar um ponto na tela) at preencher uma regio
com uma cor especfica. Para gerar melhores resultados grficos, e altamente recomendvel que se use o
driver apropriado quela placa de vdeo. Normalmente este driver fornecido pelo fabricante do hardware,
e pode facilmente instalado.
Existem livros que explicam como escrever drivers, dentre os quais destacamos [Ree10] para Windows
7 e [aAaG05] para linux.

3.6 Questes de segurana


Este livro aborda principalmente o sistema operacional linux, que um sistema desenvolvido com recursos
de segurana bem elaborados. O primeiro deles que um processo regular no tem acesso todos os recursos
do sistema, e o nico processo que tem acesso a estes recursos o sistema operacional.
Quanto a este aspecto, vale mencionar que:
1. somente entre os comandos INT e IRET que o sistema fica em modo supervisor;
2. durante este tempo, o cdigo executado cdigo do sistema operacional;
3. um processo no tem direito de escrita na regio onde se encontra o vetor de interrupes. Com isso,
ele no pode registrar um driver prprio. Isto implica dizer que o cdigo contido em um processo
regular nunca executado em modo supervisor.
3 Read

Only Memory
boot record

4 Master

CAPTULO 3. CHAMADAS DE SISTEMA

58

Porm, nem todos os sistemas operacionais seguiram o mesmo modelo, causando problemas de segurana que valem a pena ser mencionados. Um dos exemplos mais conhecidos o do sistema operacional
DOS da Microsoft.
Neste sistema operacional, os processos tinham direito de leitura e escrita toda a memria, inclusive
regio do vetor de interrupo. Quando do boot, o sistema colocava os endereos dos drivers para as
interrupes de hardware e software no vetor de interrupo. Porm, qualquer programa podia alterar o
vetor de interrupo, para que ele disparasse um cdigo especial, cuja ltima linha desviava o fluxo para o
driver apropriado como indicado na figura ??.
Aqui vai uma figura com duas colunas. A coluna da esquerda mostra uma entrada
do vetor de interrupo apontando para o seu driver. A coluna da esquerda mostra
a mesma figura, porm com a entrada apontando para outro cdigo (o intruso)
que no fim faz um jmp para o driver que estava registrado
O lado esquerdo da figura mostra como estava uma determinada entrada do vetor de interrupo antes
da alterao: o endereo contido naquela entrada o endereo de um driver do sistema.
O lado direito da figura mostra que o programa fez o seguinte:
colocou um cdigo prprio em uma regio da memria;
ajustou a entrada do vetor de interrupo para apontar para este novo cdigo;
na ltima linha do cdigo, desviou o fluxo para o driver que havia naquela entrada do vetor de
interrupo.
Este cdigo um intruso, que ser executado todas as vezes que aquela interrupo foi ativada.
interessante observar que o servio ser realizado, e no h como identificar que h um intruso (que
uma das vrias formas de se criar um vrus no MS-DOS).
Agora, vamos imaginar que este vrus faz algo maldoso. Por exemplo, ele apaga alguns caracteres
que esto apresentados na tela, ou algo mais divertido: ele faz com que as letras caiam como em um
escorregador.
Se o usurio desligar o computador e relig-lo, o vetor de interrupes ser corrigido e o problema s
voltar a existir se o usurio executar o cdigo maldoso.
Vamos supor que alm de brincar de escorregador, o vrus tambm copia o seu cdigo para dentro
de outros arquivos executveis. Desta forma, aps algum tempo, vrios outros programas executveis do
sistema estaro corrompidos com o vrus. Quando qualquer um destes programas for executado, o vrus ir
se instalar novamente substitundo aquele driver e continuar infectando outros programas.
Esta era uma realidade muito comum com o sistema operacional MS-DOS. Por vezes, um arquivo
executvel disparava (bem) mais de um vrus.
Este exemplo mostra os problemas de segurana que podem ocorrer com o uso descuidado das interrupes.
importante mencionar que o projeto do MS-DOS partiu do princpio que cada computador seria usado
somente por uma pessoa5 , com programas adquiridos sempre de empresas idnas. Em poucos anos, a realidade mudou:
1. vrias pessoas passaram a usar um mesmo computador. Algumas, com veia humorstica bastante
desenvolvida, criaram programas para pregar peas em quem usaria o programa depois deles;
2. a cpia indiscriminada de programas de um computador para outro. Se o programa estivesse infectado, o outro computador tambm seria.
3. o uso de redes de computadores, que facilitou as cpias descritas acima.
4. ao contrrio da premissa inicial, nem todas as pessoas que compraram computadores pessoais era
pessoas bem intencionadas.
Uma vantagem da abordagem do DOS era que ele permitia que cada usurio criasse sua prpria rotina
de tratamento para armadilhas, como diviso por zero (algo no muito til, na verdade). Ou ainda escrever
o seu prprio driver de tratamento de dispositivo de hardware, o que foi bastante til. Com o surgimento de
muitos dispositivos, era fcil criar um driver para cada dispositivo.
Uma coisa muito interessante criar um driver para fazer com que dois controladores de disquetes
reproduzam msicas como a marcha imperial de Star Wars, ou o tema do Super Mrio usando Stepper
Motor, entre vrias outras.
5 afinal

a sigla PC vem de Personal Computer

3.7. AS CHAMADAS AO SISTEMA NO LINUX

59

3.7 As chamadas ao sistema no Linux


O comando que faz a chamadas ao sistema no linux j foi visto: int $0x80. Porm chamar o sistema
operacional s parte da histria. Tambm necessrio indicar os parmetros da chamada.
Como foi visto no captulo 2, um processo passa os parmetros para um procedimento colocando-os na
pilha. Porm os parmetros das chamadas ao sistema adotam outro modelo: os parmetros so armazenados
em registradores.
J usamos vrias vezes uma chamada ao sistema neste texto: a chamada exit, que faz com que um
programa seja retirado de execuo. Para esta chamada usa-se os seguintes comandos assembly:
> movl $1, %eax
> movl $13, %ebx
> int $0x80
A chamada ao sistema executada pelo comando int $0x80. Os dois comandos anteriores so os
parmetros desta chamada. O valor em %eax indica qual tarefa deve ser executada (terminar a execuo do
programa) enquanto que o valor em %ebx indica qual o valor a ser colocado na varivel de ambiente $?.

3.7.1 Conjunto de servios


Cada sistema operacional apresenta um conjunto de chamadas ao sistema, e normalmente elas no so compatveis com outros sistemas operacionais.
O mundo Unix adota um conjunto de chamadas ao sistema denominado POSIX6 ortable Operating System Interface, que corresponde a um conjunto de normas definidas por um grupo de trabalho da IEEE,
chamada formalmente de IEEE 1003, mas tambm conhecida como norma internacinal ISO/IEC 9945.
Esta normatizao se fez necessria em funo dos vrios sistemas derivados de Unix que surgiram ao
longo da dcada de 70 e 80. Como no havia um padro, cada sistema era livre para adotar o conjunto
que desejasse. O problema, porm, que programas de um sistema Unix no eram compatveis com outro
sistema Unix.
O POSIX estipula um conjunto de servios que um processo pode solicitar ao sistema operacional.
O linux adota o padro POSIX. Para solicitar a execuo de um servio, o nmero do servio deve ser
indicado no registrador %eax. Abaixo, descrevemos alguns destes servios:
1 finalizar programa (exit);
2 fechar Arquivo (close);
3 ler dados de arquivo (read);
4 escrever dados em arquivo (write);
5 abrir arquivo (open);
6 fechar arquivo (open);
45 ajustar altura da heap (altera a brk)
A tabela 3.7.1 descreve estes servios com mais detalhes, como seus parmetros e funcionalidade.
O POSIX estipula um pouco menos de que duzentos servios, e a ttulo de curiosidade, descreveremos
abaixo dois servios especias que permitem a criao de um novo processo.
fork Cria um novo processo. O novo processo herda vrias caractersticas do processo criador, como descritores de arquivo, etc.). Aps a execuo desta chamada de sistema, dois processos idnticos estaro
em execuo, e exatamente no mesmo ponto do programa pai. A diferena entre eles que no processo pai, o retorno da chamada fork (em eax) o nmero do processo filho, enquanto que para o
retorno do processo filho, o retorno zero.
exec substitui o processo corrente pelo processo indicado nos argumentos. Para criar um novo processo,
necessrio primeiro fazer um fork e um exec no processo filho.
dup duplica os descritores de arquivo.
6P

CAPTULO 3. CHAMADAS DE SISTEMA

60
%eax
1

Nome
exit

read

write

close

45

brk

%ebx
retorno
descritor
arquivo

%ecx

%edx

de

incio do
buffer

tam. buffer

de

incio do
buffer

tam. buffer

descritor de
arquivo
Novo valor da
brk (Se zero,
retorna valor
atual de brk
em %eax)

incio do
buffer

tam. buffer

descritor
arquivo

Comentrio
Finaliza o programa
l para o buffer indicado
escreve o buffer para
o descritor de arquivo.
fecha o descritor de
arquivos indicado
Altera a altura da brk,
o que aumenta ou diminui a rea da heap.

Tabela 3.1: Exemplos de chamadas de sistema (syscalls).


Vrias chamadas de sistema tm funes associadas na linguagem C chamadas wrapper funcions.
Exemplos incluem write, read, open, close, fork, exec, entre outras. O funcionamento destas
funes simples: coloca-se os parmetros da funo em registradores e executa a chamada de sistema
associada. Todas estas funes esto documentadas nas man pages.
Um exemplo clssico do uso das chamadas fork e exec em programas apresentado em livros de
sistemas operacionais como modelo de shell script (veja [Tan01]).
Existe um comando para visualizar quais as chamadas de sistema que um programa executa, o comando
strace. Abaixo o resultado (resumido) do que se obtm ao analisar o comando ls aplicado em um
diretrio vazio.
> ls
> strace ls
execve("/bin/ls", ["ls"], [/* 43 vars */]) = 0
brk(0)
= 0x805c000
access("/etc/ld.so.nohwcap", F_OK)
= -1 ENOENT (No such file or ...
mmap2(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1 ...
access("/etc/ld.so.preload", R_OK)
= -1 ENOENT (No such file or ...
open("/etc/ld.so.cache", O_RDONLY)
= 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=111501, ...}) = 0
mmap2(NULL, 111501, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb7f63000
close(3)
= 0
(...)
A primeira chamada ao sistema que executada execve. Esta chamada ao sistema uma variao de
exec, que basicamente indica qual programa deve ser executado com que parmetros. Em seguida, consulta
a altura da brk (brk(0)), e assim por diante. A sada completa tem mais de 100 linhas).

Exerccios
1. Coloque um processo em execuo e anlise o arquivo /proc/PID/maps para ver o mapa de memria
deste processo.
2. Use objdump e nm para ver mapa de memria de um arquivo executvel.

Captulo 4

A Seo BSS
Suponha um programa que l uma matriz de 1024 linhas e 1024 colunas de inteiros. Em tempo de execuo,
este programa utilizar 1024 1024 = 4194304, 4Mbytes de memria s para conter esta matriz.
Se os dados forem alocados como uma varivel global, isto significa tambm que o arquivo executvel
teria, no mnimo, 4Mbytes, ocupando inutilmente muito espao em disco (afinal, estes 4Mbytes s so teis
em tempo de execuo).
Como disco normalmente um recurso escasso, e formas de diminuir o espao usado em disco (neste
caso diminuir o tamanho do arquivo executvel) so sempre bem vindas. O formato ELF (assim como quase
todos os formatos de execuo existentes) foi projetado para esta economia atravs da seo bss. A idia
bsica que o arquivo executvel contenha, na seo .bss, uma diretiva dizendo que, quando o programa for
colocado em execuo, o carregador deve alocar 4Mbytes de dados para aquela matriz. Esta diretiva ocupa
poucos bytes e com isso reduz drasticamente o espao em disco ocupado pelos arquivos executveis. J em
tempo de execuo no h nenhuma mudana (somente o local da memria virtual que a memria alocada).
Para exemplificar o funcionamento desta diretiva e a forma de utilizar a bss, o algoritmo 32 apresenta
um programa C que l um arquivo colocado no primeiro argumento e o copia para o arquivo indicado no
segundo argumento. A traduo deste algoritmo mais longa, e foi dividida em duas partes: 33 e 34.
O efeito da compilao e da execuo deste programa est apresentado abaixo.
> gcc copia.c -o copia
> ./copia SB.ps temp
> diff SB.ps temp
>
Dois aspectos devem ser observados:
1. Este programa utiliza a entrada e sada no formatadas ( read e write) para a cpia. Estas operaes so mapeadas diretamente para chamadas de sistema com o mesmo nome. importante
destacar que o procedimento printf utiliza esta mesma chamada de sistema write aps formatar os
dados para impresso.
2. Como alocar o vetor buffer. Se este vetor fosse declarado como varivel global, o arquivo executvel deveria alocar TAM_BUFFER bytes para armazenar o vetor (lembre-se que as variveis globais
ocupam espao fsico dentro do arquivo executvel). Para economizar espao, possvel declarar
este vetor dentro da rea bss e dizendo quantos bytes devem ser alocados para ele quando ele for
colocado em execuo.
O programa assembly equivalente (algoritmos 33 e 34) apresenta uma srie de novidades:
1. Constantes: A diretiva equ indica uma macro, onde o primeiro conjunto de caracteres deve ser
substitudo pelo argumento que vem depois da vrgula. Como exemplo, todos os lugares que existe
o nome SYSCALL sero substitudos por 0x80 para efeito de montagem. A idia que o programa
se torne mais legvel.

61

62

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

CAPTULO 4. A SEO BSS

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define TAM_BUFFER 256
char buffer[TAM_BUFFER];
main ( int argc, char** argv )
{
int fd_read, fd_write;
int bytes_lidos, bytes_escritos;
printf("Copiando %s em %s \n ", argv[1], argv[2]);
fd_read = open (argv[1], O_RDONLY);
if (fd_read < 0 )
{
printf ("Erro ao abrir arquivo %s \n ", argv[1]);
return -1;
}
fd_write = open (argv[2], O_CREAT | O_WRONLY );
if (fd_write < 0 )
{
printf ("Erro ao abrir arquivo %s \n ", argv[2]);
return -1;
}
bytes_lidos=read(fd_read, buffer, TAM_BUFFER);
while (bytes_lidos > 0 )
{
bytes_escritos = write (fd_write, buffer, bytes_lidos);
bytes_lidos = read(fd_read, buffer, TAM_BUFFER);
}
return 1;
}
Algoritmo 32: Programa copia.c

63

.section .data

2
3
4
5
6
7
8
9

# Constantes
.equ CLOSE_SERVICE, 6
.equ OPEN_SERVICE, 5
.equ WRITE_SERVICE, 4
.equ READ_SERVICE, 3
.equ EXIT_SERVICE, 1
.equ SYSCALL, 0x80

10
11
12
13

.equ O_RDONLY, 00
.equ O_CREAT, 0100
.equ O_WRONLY, 01

14
15
16

str1: .string "Copiando %s em %s \n "


str2: .string "Erro ao abrir arquivo %s \n "

17
18
19
20

.section .bss
.equ TAM_BUFFER, 256
.lcomm BUFFER, TAM_BUFFER

21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46

.section .text
.globl _start
_start:
pushl %ebp
movl %esp, %ebp
subl $16, %esp
pushl 16(%ebp)
pushl 12(%ebp)
pushl $str1
call printf
addl $12, %esp
movl $OPEN_SERVICE, %eax
movl 12(%ebp), %ebx
movl $O_RDONLY, %ecx
movl $0666, %edx
int $SYSCALL
movl %eax, -4(%ebp)
cmpl $0, %eax
jge abre_argv2
pushl 16(%ebp)
pushl $str2
call printf
addl $8, %esp
movl $-1, %ebx
jmp fim_pgma
Algoritmo 33: Primeira Parte da Traduo do Algoritmo 32

64

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49

CAPTULO 4. A SEO BSS


47 abre_argv2:
movl $OPEN_SERVICE, %eax
movl 16(%ebp), %ebx
movl $O_CREAT, %ecx
orl $O_WRONLY, %ecx
movl $0666, %edx
int $SYSCALL
movl %eax, -8(%ebp)
cmpl $0, %eax
jge primeira_leitura
pushl 12(%ebp)
pushl $str2
call printf
addl $8, %esp
jmp fim_pgma
primeira_leitura:
movl $READ_SERVICE, %eax
movl -4(%ebp), %ebx
movl $BUFFER, %ecx
movl $TAM_BUFFER, %edx
int $SYSCALL
movl %eax, -12(%ebp)
while:
cmpl $0, -12(%ebp)
jle fim_while
movl $WRITE_SERVICE, %eax
movl -8(%ebp), %ebx
movl $BUFFER, %ecx
movl -12(%ebp), %edx
int $SYSCALL
movl %eax, -16(%ebp)
movl $READ_SERVICE, %eax
movl -4(%ebp), %ebx
movl $BUFFER, %ecx
movl $TAM_BUFFER, %edx
int $SYSCALL
movl %eax, -12(%ebp)
jmp while
fim_while:
movl $CLOSE_SERVICE, %eax
movl -4(%ebp), %ebx
int $SYSCALL
movl $CLOSE_SERVICE, %eax
movl -8(%ebp), %ebx
int $SYSCALL
movl $1, %ebx
fim_pgma:
movl $EXIT_SERVICE, %eax
int $SYSCALL
Algoritmo 34: Segunda Parte da Traduo do Algoritmo 32

4.1. HEAP

65

2. A seo .bss contm uma macro (TAM_BUFFER) equivalente ao programa em C e em seguida o


comando .lcomm BUFFER, TAM_BUFFER que indica que em tempo de execuo, devero ser
alocados 256 bytes ao rtulo BUFFER. Dentro do arquivo executvel existe somente uma referncia
necessidade de criao de 256 bytes no topo da seo bss (veja com objdump -t).
Em alguns casos, o programador quer que algumas de suas variveis globais sejam alocadas na BSS.
A forma de fazer isso varia de linguagem de programao para linguagem de programao. Na linguagem
C, isto obtido colocando a palavra reservada static na frente da declarao da varivel. Por exemplo:
...
static int alocaNaBSS;
...
Por motivos bvios, a BSS no pode ter variveis iniciadas. Por esta razo, a declarao
...
static int alocaNaDATA=10;
...
ir alocar a varivel alocaNaDATA ser alocada na seo .data e no na seo .bss.

4.1 Heap
O nome heap est associado a alocao dinmica. neste local em que alocado espao atravs das funes
malloc e seus assemelhados. Porm, o modelo de execuo no tem uma seo heap. Este espao alocado
imediatamente acima da rea da bss.
Devido a esta proximidade, os dois nomes so freqentemente usados indistintamente, apesar de serem
conceitualmente diferentes: a rea da bss esttica (em nosso exemplo, um bloco de 256 bytes foi alocado
no incio da execuo, e no ser liberado at o fim da execuo do programa). Por outro lado, a heap pode
crescer e decrescer durante a execuo do programa.
Considere mais uma vez o modelo de execuo de um programa. As reas de texto, dados e bss so
estticas (no mudam de tamanho ao longo da execuo), enquanto que a pilha pode crescer ou decrescer. A
rea da pilha em que um programa pode acessar definida pelo registrador %esp, ou seja, o programa tem
acesso rea definida entre o topo da memria virtual at o endereo contido em %esp. Tentar acessar o
endereo -12(%esp) ocasiona um erro de execuo com simptica mensagem segmentation fault1 .
O topo da heap no armazenado em registrador, mas sim em uma estrutura de dados interna do Sistema
Operacional (por vezes chamada de brk, e somente ele pode alterar o valor. No incio da execuo do
programa, o topo da heap definido como o topo da bss (ou seja, est vazio). Quando o programa deseja
alocar alguns bytes dinamicamente, ele basicamente precisa solicitar que o SO altere o valor da brk.
Como a nica forma de se comunicar com o Sistema Operacional atravs de chamadas de sistema, o
programa pode fazer uso do servio nmero 45, indicando o novo endereo da brk em %ebx. O endereo
do novo valor do topo da heap retornado em %eax. Se o valor em %ebx for zero, somente retornado o
endereo atual da brk.
importante destacar que as funes de C que fazem uso da heap (malloc, free, etc.) so gerenciadores
deste espao, e por isso nem sempre um malloc termina com uma chamada ao sistema. Por exemplo, quando
o programa solicita 100 bytes, o malloc pode (e deve) alocar mais do que isso (por exemplo, 1024 bytes ou
uma pgina de memria). A segunda chamada a malloc no precisar usar a chamada ao sistema se o espao
alocado anteriormente no foi totalmente utilizado.
De forma anloga, um free nem sempre libera o espao da heap (neste caso, o espao apagado logicamente atravs de um indicador de espao livre). Por isso, se um programa fizer seguidamente mallocs, a
heap cresce, mas os frees nem sempre fazem a heap decrescer.
1 Teoricamente, isso mesmo, mas nem sempre assim na prtica, pois o custo para implementar este
teste normalmente caro. Por isso, a maioria dos Sistemas Operacionais implementam esta regra com
mecanismos mais simples, o que pode permitir a violao em alguns casos. Esta uma das razes para que
alguns programas funcionem (ou deixem de funcionar) ao acrescentar ou retirar uma varivel (ou mesmo um
byte) regio de dados.

CAPTULO 4. A SEO BSS

66

4.2 Exerccios
1. Acrescentando rede ao parquinho. O parquinho tambm tem uma caixa de correio. Para que as cartas
possam ser direcionadas s crianas certas, cada uma delas pode pendurar uma meia (socket),
como ocorre no natal, e dizer supervisora que as mensagens que chegarem quela meia devem
ser colocas na caixa de areia dela. Descreva como acrescentar este novo dispositivo ao parquinho.
Depois disso, acrescente um mecanismo que permite que as crianas tambm enviem mensagem pelo
carteiro.
2. Estenda a analogia para mltiplas CPUs.
3. threads Estenda a analogia do parquinho para que mais de uma criana possa brincar na mesma caixa
de areia (s uma delas iluminada pela fadinha). Quais os problemas que podem ocorrer? Como
previn-los?

Parte II

Software Bsicos

67

69
Este texto considera como software bsico aquele que converte arquivos associados execuo de
programas para outro tipo de arquivo, cujo contedo mais prximo ao modelo de execuo.
O captulo 5 descreve sucintamente alguns destes programas. Cada programa recebe um arquivo como
entrada e gera um arquivo de sada. Os formatos dos arquivos de entrada e de sada so descritos no captulo 6.
Dos programas indicados no captulo 6, dois se destacam, e so tratados em sees prprias: o programa
ligador (captulo 6.4) e o programa carregador (captulo 6.5).

70

Captulo 5

Software Bsicos
Entende-se como software bsicos aqueles que convertem arquivos associados execuo de programas de
um formato para outro mais prximo ao formato executvel compreendido pela mquina. Os formatos e
software esto descritos esquematicamente na figura 5.1. Observe que um programa em linguagem de alto
nvel visto como um programa executvel pelo programador, porm para que a mquina consiga coloclo em execuo, necessrio converter da linguagem entendida pelo programador (linguagem de alto nvel)
para linguagem de mquina (linguagem de baixo nvel). Quem faz esta converso so o compilador e o
ligador.
Uma breve descrio das ferramentas apresentada a seguir:

Compiladores: Compiladores so programas que convertem arquivos que contm programas escritos em
linguagens de alto nvel (formato texto) para formato objeto. Exemplos de linguagens de alto nvel
incluem C, Java, Fortran, Pascal, entre outros.
Montadores: Montadores so programas que convertem arquivos que contm programas em linguagem
assembly (formato texto) para o formato de cdigo objeto.
Ligadores: so programas convertem arquivos em formato objeto para formato executvel.
Carregadores : Carregam os arquivos que contm formato executvel para a execuo. Para isto ele l
partes dos arquivos objeto e copia estas partes para a memria.
O restante desta seo detalha o funcionamento, a forma de se utilizar e a funcionalidade destas ferramentas.
Compilador, montador e ligador so programas que podem ser invocados diretamente pelo programador.
Como exemplo destes programas e do efeito que eles produzem, digite e salve o algoritmo 35 abaixo
em um arquivo, por exemplo, prog1.c.

1
2
3
4
5

int a, b;
main ( int argc, char** argv);
{
return 13;
}
Algoritmo 35: Arquivo prog1.c

Em seguida, compile o programa para obter o arquivo objeto (prog.o). Por fim, use o arquivo objeto
para gerar o executvel como descrito abaixo.

71

CAPTULO 5. SOFTWARE BSICOS

72

Figura 5.1: Formatos de arquivos executveis e os software de converso para o


formato executvel
> gcc -c prog1.c -o prog1.o
> gcc prog1.o -o prog1
> ./prog1
> echo \$?
13
Os arquivos prog1.c, prog1.o e prog1 so respectivamente os programas fonte, objeto e executvel sendo que cada um est armazenado em um formato especfico.
O arquivo prog1.c (programa fonte) segue o formato especificado da linguagem C.
O arquivo prog1.o e o arquivo prog1 esto em um formato que no usa somente caracteres ASCII, e
por isso ilegvel para a maior parte da raa humana. Porm, existem programas que imprimem o contedo
deste tipo de arquivo para uma linguagem que utiliza caracteres ASCII (porm no por isso so mais legveis).
Um exemplo o programa objdump, que pode ser usado em arquivos ELF objeto e executveis. Exemplo:
> gcc -c prog1.c -o prog1.o
> objdump -s prog1.o
prog1.o:

file format elf32-i386

Contents of section .text:


0000 8d4c2404 83e4f0ff 71fc5589
0010 00000000 06000000 c7050000
0020 00008b15 00000000 a1000000
0030 a3000000 00a10000 0000595d
Contents of section .comment:
0000 00474343 3a202847 4e552920
0010 31203230 30373031 30352028
0020 48617420 342e312e 312d3531

e551c705
00000700
008d0402
8d61fcc3

.L$.....q.U..Q..
................
................
..........Y].a..

342e312e
52656420
2900

.GCC: (GNU) 4.1.


1 20070105 (Red
Hat 4.1.1-51).

Como foi dito antes, a impresso no exatamente o que se pode chamar de legvel.
Uma outro aspecto importante que o formato do arquivo objeto e executvel definido pelo Sistema
Operacional. No linux, este formato o ELF (Executable and Linking Format), que foi originariamente
desenvolvido e publicado pelo UNIX System Laboratories (USL) como parte da Application Binary Interface
(ABI).

73
J para o Sistema Operacional Windows foram projetados vrios modelos de execuo. Como exemplos,
temos o formato COM, EXE e mais recente o PE (Portable Executable File Format.
Como quem define o formato do arquivo executvel o Sistema Operacional, programas executveis gerados em um sistema operacional no pode ser executado diretamente em outro sistema operacional (mesmo
que ambas as mquinas usem o mesmo processador). Quando tal se faz necessrio, comum usar interpretadores ou emuladores do modelo executvel em questo. Uma forma bastante esperta de se fazer isso ler
o o arquivo em formato executvel A e colocam em execuo no formato executvel B. assim que o
wine1 trabalha.
Este texto ir detalhar alguns aspectos do formato de arquivos ELF. Sugerimos ao leitor mais curioso
que digite objdump -s prog1 para ver as sees em que o arquivo prog1 est dividido (section .init,
.plt, .text, .fini). Destas, destacamos a seo .text, que contm os comandos em linguagem de mquina para
o referido programa.
Um carregador uma parte do Sistema Operacional que capaz de ler um arquivo executvel e coloc-lo
na memria para execuo. Basicamente ele executa as seguintes tarefas:
1. identifica as sees de um arquivo executvel;
2. solicita uma nova entrada na tabela de processos do SO;
3. aloca espao na memria para que o programa seja l colocado;
4. termina sua ao.
Ao final desta seqncia, o Sistema Operacional ter um novo processo na tabela de processos, e o
escalonar para execuo de acordo com as regras de escalonamento de processos.
comum encontrar dois tipos de carregadores: os carregadores absolutos e os relocveis. Os carregadores absolutos alocam sempre a mesma poro de memria para um mesmo processo.
Isso parece estranho, e um exemplo pode ajudar. Todos os programas executveis tem comandos assembly. Um dos comandos assembly que permitem o desvio do fluxo de execuo o comando do tipo jump
ENDEREO. Quando se usa carregadores absolutos, o compilador precisa assumir que o programa comea
em um determinado endereo da memria fsica e assim o ENDEREO contido no jump corresponde a
um endereo fsico de memria conhecido ANTES de o programa ser colocado em execuo. Com isso, o
programa tem que ser colocado sempre no mesmo local da memria fsica para funcionar corretamente. Se
esta regio de memria estiver sendo ocupada, o Sistema Operacional adota uma das seguintes medidas:
1. retira o outro processo da memria (copia o outro processo para uma rea em disco, chamada memria swap) e assim libera a rea desejada para o novo processo;
2. impede a execuo do novo processo.
Como de se imaginar, a primeira soluo a mais comum.
O segundo tipo de carregador o carregador relocvel. Neste caso, o programa pode ser colocado
em qualquer local da memria para execuo. O programa executvel relocvel semelhante ao programa
executvel absoluto, exceto que os endereos de memria que ele referencia so ajustados para o endereo
de memria correto quando ele colocado para execuo (na verdade, este ajuste pode ocorrer tanto em
tempo de carregamento ou em tempo de execuo). Como estes endereos so ajustveis, recebem o nome
de endereos relocveis. Esta soluo to comum que muitos processadores incluem facilidades para
determinar os endereos relativos, facilitando a relocao em tempo de execuo.
Os programas DOS com extenso .exe so exemplos de programas executveis relocveis, enquanto
que os que tem extenso .com usam relocador absoluto.
Apesar de mais flexveis, os carregadores relocveis tambm apresentam problemas:
Os programas a serem executados no podem ser maiores do que a memria disponvel. Ou seja, se
o computador tiver 128M de memria RAM, ele no poder executar programas que ocupem 129M.
Se a memria estiver ocupada por outros programas, (ou seja, h memria mas no est livre), estes
programas tero de ser descarregados antes da execuo do novo programa.
Vrias abordagens foram tentadas para minimizar os problemas de memria citados acima, e as solues
propostas nem sempre so convincentes2 .
1 http://www.winehq.com/
2 Como aconteceu em uma estria de Asterix onde o druida Amnesix curou uma pessoa que pensava
ser um Javali. Ele o ensinou a ficar de pezinho, e assim notava-se menos o problema (veja historia completa
em Asterix e o Combate dos Chefes).

74

CAPTULO 5. SOFTWARE BSICOS

importante destacar que um dos maiores (seno o maior) problema para executar um processo3 ,
alocar espao para ele na memria fsica. Cada sistema operacional tem seus prprios mecanismos mas
estes mecanismos no sero detalhados aqui, pois so um tema longo normalmente abordado em livros de
Sistemas Operacionais.
Primeira Soluo jogar o problema para o usurio: exigir que o computador tenha muita memria. Apesar
de parecer pouco vivel comercialmente, esta soluo era muito comum, e para isto basta veja a
configurao mnima exigida por certos programas4 . Apesar de simples, ela pode comprometer as
vendas dos produtos, principalmente se o programa precisar de uma grande quantidade de memria.
No necessrio dizer que esta soluo no deixa os usurios felizes.
Segunda Soluo jogar o problema para os programadores. Aqui, vrias opes foram tentadas, mas a que
mais vivel divide o programa em vrias partes logicamente relacionadas. Como exemplo, considere
os programas de cadastro que normalmente comeam com uma tela que solicita que o usurio escolha
por um opo entre incluso, alterao e excluso. As trs opes so, normalmente, implementados
em trechos diferentes de cdigo, e no precisam estar simultaneamente na memria. Neste tipo de
aplicao, o programador indica quais so os trs mdulos e que eles devem ser carregados de forma
excludente. Esta soluo tambm est longe de ser a ideal, mas era encontrada freqentemente em
programas da dcada de 70-80, quando os computadores tinham pouco espao de memria (por
exemplo 64K de memria RAM). Esta abordagem remonta ao conceito de overlays, assunto de
disciplinas de Sistemas Operacionais e foi adotado em vrios Sistemas Operacionais.
Esta idia semelhante ao modelo de bibliotecas carregadas dinamicamente, onde o programador
cria um programa principal (que pode ficar o tempo todo na memria) e as bibliotecas (subrotinas
chamadas somente quando necessrio). Em tempo de execuo, o programa indica quais bibliotecas
devem ser carregadas na memria, e o carregador (por vezes chamado de carregador-ligador) traz a
biblioteca requisitada para a memria. Aps o uso, o programador pode dizer ao Sistema Operacional
que aquela biblioteca pode ser descarregada da memria.
Terceira Soluo assumir que o sistema operacional que o responsvel pelo gerenciamento da memria,
esta abordagem a adotada pelos sistemas operacionais baseados em unix. O sistema operacional
gerencia a memria por meio de um mecanismo chamado paginao.
A idia bsica da paginao que um programa no acessa diretamente os endereos reais (fsicos
- na memria), mas sim endereos virtuais. Trechos dos programas em memria virtual so ento
copiados para a memria real, e, cada vez que um endereo acessado, o SO primeiro faz a converso
de endereos para descobrir a qual endereo fsico corresponde cada endereo virtual. Este processo
lento, e por isso muitas CPUs j incorporaram este mecanismo para ganhar desempenho.
Mais detalhes sobre o funcionamento deste mecanismo fogem ao escopo deste livro. Para mais
informaes, consulte livros de sistemas operacionais ou de arquitetura de computadores.

3 Na taxonomia de sistemas operacionais, um processo um programa pronto para execuo ou em execuo.


4 Isto no quer dizer que todos os programas que apresentam uma configurao mnima recaem neste
caso. Por vezes, ela indica o tamanho mnimo de memria necessrio para que partes bsicas (aquelas que
tem de ficar na memria ao longo de toda a execuo do programa).

Captulo 6

Formatos de Programa
Um programa de computador pode ser visto de vrias formas diferentes de acordo com o software bsico que
pode ser utilizado sobre ele. Estes formatos so: linguagem de alto nvel (C, C++, Pascal, Fortran, Cobol),
linguagem assembly e linguagem de mquina (objeto e executvel). Descrevemos cada uma destas a seguir.

6.1 Linguagem de alto nvel


As linguagens de alto nvel so as mais compreensveis para os seres humanos, porm para a mquina so
somente um conjunto de caracteres. Programas escritos em linguagens de alto nvel podem ser transformados
em linguagens mais prximas linguagem que o computador compreende atravs do compilador.
A vantagem de escrever programas neste formato que o programador no precisa conhecer as peculiaridades da arquitetura em que este programa ser executado como, por exemplo, o conjunto de registradores,
conjunto de instrues, interrupes, etc. Existem duas desvantagens em usar o compilador:
1. o compilador normalmente inclui uma srie de procedimentos que muitas vezes no so utilizados;
2. o compilador por vezes pode gerar um cdigo mais lento do que seria se fosse gerado em linguagem
de mquina (no devemos ficar chateados com o compilador por isso, ele deve ser conservador por
natureza, ou seja, ele deve gerar um cdigo que funciona, no obrigatoriamente o cdigo mais rpido
que funciona).
Tambm importante destacar que estas duas desvantagens so na verdade um preo muito barato a se
pagar quando os programas so grandes (e em muitos casos tambm barato para programas pequenos).
A seguir, apresentamos um exemplo de um programa em linguagem de alto nvel e as diretivas de
compilao que o transformam em um programa em linguagem executvel. Para ilustrar melhor algumas
peculiaridades envolvidas com o processo, apresentamos um programa composto de dois arquivos descritos
em 36 e 37.

1
2
3
4
5
6

void a (char* s);


int main (int argc, char** argv)
{
static char str[] = "Hello World\n";
a (str);
}
Algoritmo 36: Programa exemplo: arquivo main.c
Para compilar os dois arquivos em um arquivo executvel, usamos os seguintes comandos:

75

CAPTULO 6. FORMATOS DE PROGRAMA

76

1
2
3
4
5

#include <stdio.h>
void a (char* s)
{
printf("%s", s);
}
Algoritmo 37: Programa exemplo: arquivo a.c

> gcc -o a.o -c a.c


> gcc -o main.o -c main.c
> gcc -o main main.o a.o
> ./main
Hello World
>
As duas primeiras linhas geram o cdigo objeto (extenso *.o) que corresponde a cada arquivo com
extenso .c. A terceira linha agrupa os dois arquivos objeto em um nico arquivo executvel.
Existem mtodos alternativos:
1. Gerando um nico arquivo objeto.
> gcc -o a.o -c a.c
> gcc -o main main.c a.o
2. No gerando explicitamente nenhum arquivo objeto.
> gcc -o main main.c a.c
Observe que em nenhum caso foi utilizado o ligador. O processo de compilao procura simplificar o
trabalho do programador, e por isso o programa gcc chama automaticamente o ligador (programa ld)
para que o programa executvel seja gerado a partir do programa objeto.
Um aspecto importante que nem o arquivo main.o e nem o arquivo a.o podem ser executados.
Somente o arquivo main que pode ser executado, uma vez que est no formato que compreendido pelo
programa carregador.
Os dois tipos de arquivo (objeto e executvel) esto no formato ELF (estamos falando sobre Linux,
especificamente) e so arquivos binrios. Cada arquivo dividido em sees, e as diferenas entre eles
podem ser vistas atravs de um programa que transforma o arquivo binrio em um arquivo texto com os
comandos em assembly (chamado disassembly). Um exemplo deste tipo de programa para Linux o
objdump. Como exerccio, digite os programas acima e em seguida:
> objdump -S a.o
> objdump -S main
O opo -S faz com que o cdigo executvel seja impresso em formato texto, uma instruo por linha.

6.2 Linguagem assembly


Os primeiros programas eram escrito em cdigo de mquina, e os programadores da poca deviam indicar
explicitamente quais bits da memria deveriam estar apagados e quais deveriam estar ligados. Este
processo era muito propenso a erros, e por isso, foi projetada a linguagem assembly. Como regra geral, cada
instruo em assembly representa exatamente uma instruo em linguagem de mquina (a que executada
diretamente pela CPU). O programa que traduz o arquivo contendo instrues em assembly para linguagem
de mquina chamado de montador, que um programa bastante simples pois a correspondncia entre
instrues em assembly e linguagem de mquina quase sempre de um para um.
Um programa assembly um conjunto de caracteres ASCII, porm cada linha corresponde a uma instruo em linguagem de mquina. Como cada arquitetura tem o seu prprio conjunto de instrues (definido

6.2. LINGUAGEM ASSEMBLY

77

no processador utilizado), para converter um programa em assembly que executa no processador X para o
processador Y necessrio reescrev-lo totalmente.
Freqentemente os prprios fabricantes de CPU vendem tambm os programas montadores para as suas
CPUs. Existem excesses a esta regra, mas neste caso pelo menos fornecido um manual que explica o
mapeamento para que terceiros possam escrever montadores.
Em funo da complexidade e detalhamento envolvido na programao de computadores via linguagens
assembly, elas comearam a cair em desuso quando surgiram as primeiras linguagem de alto nvel, onde uma
linha de cdigo pode corresponder a dezenas de linhas de programas assembly.
Quando isso ocorreu, programas que eram antes escritos por um complexo cdigo de milhares (ou
milhes) de linhas passaram a ser escritos por algumas centenas de linhas de cdigo em linguagens de alto
nvel, que alm de serem mais fceis de ser projetados, so tambm mais fceis de serem depurados e de se
dar manuteno.
importante destacar que as linguagens de alto nvel so convertidas para assembly (ou diretamente
para linguagem de mquina) para que possam ser executadas em uma mquina especfica. Neste caso, os
programas em linguagem assembly (ou em linguagem de mquina) so equivalentes (ou seja, causam o
mesmo efeito) s linguagens de programao em linguagem de alto nvel
O cdigo do algoritmo 38 um exemplo de programa assembly.

1
2
3
4
5
6
7

.section .data
.section .text
.globl _start
_start:
movl $1, %eax
movl $13, %ebx
int $0x80
Algoritmo 38: Exemplo de programa assembly (exit.s).

Uma diferena importante entre programas escritos em linguagem assembly e programas escritos em
linguagens de alto nvel que os primeiros lidam diretamente com os objetos de armazenamento do computador (registradores e memria). O conceito de varivel um conceito abstrato, e est associado com um
local de armazenamento, mas sem explicitar qual (por exemplo, se em memria ou em registrador).
At certo ponto, podemos considerar que programas assembly lidam com objetos concretos, enquanto
que linguagens de alto nvel lidam com objetos abstratos. Linguagens como as orientadas a objeto lidam
com objetos ainda mais abstratos, como objetos, classes, mtodos, entre outros.
Em linguagens de alto nvel, o conceito de varivel abstrato, mas inclui outros aspectos, como por
exemplo, um conjunto de atributos, como tipo, tamanho, entre outros. Como cada varivel de um tipo
especfico, o compilador se recusa a gerar cdigo executvel se o programa fonte misturar tipos de dados
(por exemplo, se tentar somar um inteiro com um caractere). Em assembly, isto possvel, por exemplo
somando um inteiro de oito bits (um caractere) com um inteiro de 32 bits (um inteiro com ou sem sinal).
Alm disso, em assembly se utilizam instrues onde o tipo est implcito: somar dois valores assumindo
que os dois so inteiro com ou sem sinal. O programador no se preocupa com isso em linguagens de alto
nvel.
Voltemos agora a diferenas mais concretas entre linguagens de alto e de baixo nvel. O processo de
gerao de programa executvel a partir de um programa em assembly est descrito abaixo:
> as -o exit.o exit.s
> ld -o exit exit.o
> ./exit
>
Compare este processo com a figura 5.1). A primeira linha gera um arquivo objeto exit.o a partir do
arquivo de entrada exit.s usando o programa montador (as neste caso). A segunda linha gera o arquivo
executvel exit a partir do arquivo objeto exit.o atravs do ligador (ld neste caso). Por fim, a terceira
linha executa o programa. Este programa no gera nenhum resultado na tela como pode ser visto.

CAPTULO 6. FORMATOS DE PROGRAMA

78

6.3 Linguagem de Mquina


A linguagem de mquina pode ser encontrada tanto em arquivos executveis, abordado na seo 6.3.2.1,
quanto em arquivos objeto, abordado na seo 6.3.2.1.

6.3.1 Linguagem de Mquina - Arquivo executvel


O arquivo executvel em linguagem de mquina contm instrues em uma linguagem que a mquina compreende, mas que incompreensvel para os seres humanos (bom, pelo menos para a grande maioria). O
formato do arquivo executvel (ou seja, como ele organizado, quais as informaes que ele contm, e
como esto organizadas) definido pelo sistema operacional (e no pela CPU, como muitos podem imaginar). Um programa (o carregador) capaz de ler um arquivo neste formato e coloc-lo em execuo.
Os arquivos que contm programas neste formato esto representados em binrio (normalmente compactado na representao em hexadecimal). Um exemplo de arquivo binrio (em hexadecimal), que corresponde ao programa "exit", montado acima, mostrado a seguir:
> hexdump
00000000
00000010
00000020
00000030
00000040
00000050
00000060
00000070
00000080
00000090
000000a0
000000b0
000000c0
000000d0
000000e0
000000f0
00000100
00000110
00000120
00000130
00000140
00000150
00000160
00000170
00000180
00000190
000001a0
000001b0
000001c0
000001d0
000001e0
000001f0
00000200
00000210
00000220
00000230
00000240
00000250
00000260
00000270
00000280
00000290
00000294

exit -C -v
7f 45 4c 46
02 00 03 00
ac 00 00 00
07 00 04 00
00 80 04 08
00 10 00 00
80 90 04 08
00 10 00 00
00 2e 73 79
00 2e 73 68
00 2e 64 61
00 00 00 00
00 00 00 00
00 00 00 00
74 80 04 08
00 00 00 00
01 00 00 00
00 00 00 00
00 00 00 00
80 90 04 08
00 00 00 00
03 00 00 00
2c 00 00 00
00 00 00 00
00 00 00 00
07 00 00 00
03 00 00 00
20 00 00 00
00 00 00 00
00 00 00 00
03 00 01 00
03 00 02 00
03 00 03 00
03 00 04 00
03 00 05 00
03 00 06 00
10 00 01 00
10 00 f1 ff
10 00 f1 ff
10 00 f1 ff
73 5f 73 74
65 6e 64 00

01
01
00
01
80
01
00
b8
6d
73
74
00
00
1b
74
04
03
00
27
80
04
00
00
01
c4
04
00
00
00
00
00
00
00
00
00
01
08
14
1b
00
61

01
00
00
00
00
00
00
01
74
74
61
00
00
00
00
00
00
00
00
00
00
00
00
00
01
00
00
00
00
00
00
00
00
00
00
00
00
00
00
5f
72

01
00
00
00
00
00
00
00
61
72
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
73
74

00
00
00
00
00
00
00
00
62
74
2e
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
74
00

00
74
34
00
80
80
00
00
00
61
62
00
00
01
0c
00
80
00
08
00
00
00
00
02
b0
10
00
00
00
74
80
80
00
00
00
74
80
80
80
61
5f

00
80
00
00
00
00
00
bb
2e
62
73
00
00
00
00
00
90
00
00
00
00
00
00
00
00
00
00
00
00
80
90
90
00
00
00
80
90
90
90
72
65

00
04
20
00
00
00
00
03
73
00
73
00
00
00
00
00
04
00
00
00
00
00
00
00
00
00
00
00
00
04
04
04
00
00
00
04
04
04
04
74
64

00
08
00
00
00
00
00
00
74
2e
00
00
00
00
00
00
08
00
00
00
00
00
00
00
00
00
00
00
00
08
08
08
00
00
00
08
08
08
08
00
61

00
34
02
00
05
80
06
00
72
74
00
00
00
06
00
21
80
04
03
00
11
80
01
00
06
09
74
01
00
00
00
00
00
00
00
00
00
00
00
5f
74

00
00
00
80
00
90
00
00
74
65
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
02
00
00
00
00
00
00
00
00
00
00
00
00
5f
61

00
00
28
04
00
04
00
cd
61
78
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
62
00

00
00
00
08
00
08
00
80
62
74
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
73
5f

|.ELF............|
|........t...4...|
|........4. ...(.|
|................|
|................|
|................|
|................|
|................|
|..symtab..strtab|
|..shstrtab..text|
|..data..bss.....|
|................|
|................|
|................|
|t...t...........|
|............!...|
|................|
|................|
|...............|
|................|
|................|
|................|
|,...............|
|................|
|................|
|................|
|............t...|
| ...............|
|................|
|........t.......|
|................|
|................|
|................|
|................|
|................|
|........t.......|
|................|
|................|
|................|
|....._start.__bs|
|s_start._edata._|
|end.|

A primeira coluna indica os endereos, a segunda indica o contedo de cada endereo em formato
hexadecimal enquanto que a ltima coluna indica a representao ASCII para cada conjunto de oito bits.
Exemplo: endereo 00000001 = (45)hexa = (E) em ASCII.

6.3. LINGUAGEM DE MQUINA

79

O arquivo executvel apresentado est no formato ELF (Executable and Linking Format), um formato
desenvolvido pela Sun. Devido sua flexibilidade e documentao livre, este formato foi adotado em vrios
(se no em todos) sistemas operacionais baseados em Unix. Existem dois tipos de arquivos ELF: os que
armazenam cdigo objeto (incluindo bibliotecas) e os que armazenam cdigo executvel (que o caso do
exemplo acima).
Para executar um programa ELF, o usurio digita somente o nome do arquivo, e o sistema operacional
ativar o carregador, um programa interno que l este arquivo binrio e executa uma srie de passo para
coloc-lo em execuo. Para matar a curiosidade sobre o formato, veja [Wheb] (que apresenta bem mais do
que s o formato ELF). Um arquivo executvel no formato ELF est dividido em sees (.symtab, .strtab,
.shstrtab, .text, .data, .bss, etc.). Uma destas sees, a seo .text, contm as instrues em linguagem de
mquina que so compreendidos pela CPU.
Muitas vezes h interesse em ver os mnemnicos ASCII para cada uma das instrues em assembly, e
por isso foram criados programas que "desmontam" (disassembly) os arquivos executveis. Usaremos aqui
o programa "objdump". Considere como exemplo o programa "exit", que contm somente trs instrues.
> objdump -S exit
exit:

file format elf32-i386

Disassembly of section .text:


08048054 <_start>:
8048054: b8 01 00 00 00
8048059: bb 0d 00 00 00
804805e: cd 80

mov
mov
int

$0x1,%eax
$0xd,%ebx
$0x80

Esta somente uma representao mais amigvel do programa executvel. A visualizao mais fiel
aquela apresentada anteriormente. Cada instruo assembly (normalmente) corresponde a uma instruo em linguagem de mquina. Neste caso, a representao em linguagem de mquina da instruo mov
$0x1,%eax o padro hexadecimal b8 01 00 00 00. Para ver mais sobre formatos de instruo assembly, modos de endereamento dos parmetros, etc. veja o apndice A.1.
Atravs do programa objdump, tambm possvel selecionar partes dos arquivos binrios para serem
mostrados "in natura". Um exemplo mostrar o cdigo binrio da seo .text:
> objdump -s exit
exit:

file format elf32-i386

Contents of section .text:


8048054 b8010000 00bb0d00 0000cd80

............

possvel identificar a instruo mov $0x1,%eax codificada no padro b801000000.

6.3.2 Linguagem de Mquina - Arquivo objeto


O formato ELF tambm comporta uma representao para arquivos objeto. Um exemplo de arquivo objeto
o arquivo "a.o" apresentado acima, gerado a partir do arquivo-fonte "a.c". Basta olhar para o arquivo a.c para
perceber que ele no passvel de ser executado diretamente, uma vez que no apresenta a funo "main".
interessante observar que o arquivo "main.o" contm a funo "main", porm no possvel criar um
arquivo executvel a partir dele pois no est includa a implementao da funo "a".
Assim, os arquivos em linguagem de mquina no formato objeto so incompletos, ou seja, falta-lhes
algo para serem executados. Este algo pode ser a funo "main" ou a definio de alguma varivel ou
funo externa.
Observe que pode-se gerar um programa executvel usando vrios arquivos objeto atravs do ligador.
No exemplo acima, os arquivos "main.c" e "a.c", geraram dois arquivos objeto: "main.o" e "a.o", que
foram ligados e geraram um nico arquivo executvel. Isto foi possvel porque os arquivos se completam:

CAPTULO 6. FORMATOS DE PROGRAMA

80

o arquivo "main.o" contm a funo "main" enquanto que o arquivo "a.o" contm a implementao da
funo "a".
O uso de arquivos objeto agiliza em muito o processo de desenvolvimento de software. Se um software
for composto de vrios mdulos, cada mdulo composto de vrias funes, razovel dividir o programa em
vrios arquivos menores e s junt-los para gerar o arquivo executvel. Se houver necessidade de modificar
um destes arquivos, por exemplo para corrigir um erro (bug), ento no h necessidade de alterar os demais
arquivos.
Como exemplo, suponha que um programa tenha sido dividido em cinco arquivos: a.c, b.c, c.c, d.c,
e main.c. O arquivo main.c utiliza as funes implementadas nos outros arquivos, e o arquivo executvel
"main" obtido atravs dos seguintes comandos:
>
>
>
>
>

gcc
gcc
gcc
gcc
gcc

-o
-o
-o
-o
-o

a.o -c a.c
b.o -c b.c
d.o -c c.c
d.o -c d.c
main main.c a.o b.o c.o d.o

Se na fase de testes for detectado que h um erro em alguma das funes implementadas no arquivo
"c.c", ento o programador deve somente alterar este arquivo e para testar se o erro foi corrigido, deve
executar os seguintes comandos:
> gcc -o c.o -c c.c
> gcc -o main main.c a.o b.o c.o d.o
Observe que no foi necessrio recompilar os arquivos objeto a.o, b.o e d.o, pois estes no sofreram
modificaes. Uma idia interessante seria a de agrupar os arquivos objeto em um nico arquivo, chamado
biblioteca.

6.3.2.1 Bibliotecas
O uso de arquivos objeto, reduz o tempo e a complexidade de construo de programas, especialmente se
as funes contidas em arquivos objeto j tiverem sido testadas exaustivamente. Neste caso, as funes
em arquivos-objeto so normalmente mais confiveis do que projetar estas funes, alm de ser muito mais
rpido.
Como exemplo, suponha que algum foi encarregado de desenvolver um programa onde necessrio
calcular a raiz quadrada de um nmero. Se um programador souber que esta funo est presente em algum
arquivo objeto, ele ganhar tempo utilizando esta implementao ao invs de implement-la.
Como isso j foi observado h muito tempo, j foram desenvolvidos trechos de programa para vrias
operaes, e foram gerados arquivos objeto para elas. Porm, por uma questo de organizao, interessante
agrupar arquivos objeto em um nico arquivo para somente ento inclu-los nos programas. Este arquivo que
contm uma srie de arquivos objeto chamado de biblioteca. A presente seo detalha os trs tipos de
bibliotecas disponveis, que se diferenciam principalmente nos seguintes aspectos:
1. na forma com que so geradas;
2. na forma com que so invocadas em tempo de execuo;
3. no espao fsico que ocupam no arquivo executvel e na memria.
As prximas sees descrevem cada um dos tipos de bibliotecas, destacando em especial os aspectos supracitados. A seo 6.3.2.2 descreve as bibliotecas estticas, a seo 6.3.2.3 descreve as bibliotecas
compartilhadas e a seo 6.3.2.4 descreve as bibliotecas dinmicas.

6.3.2.2 Bibliotecas Estticas


Uma biblioteca esttica um conjunto de arquivos objeto agrupados em um nico arquivo. Normalmente se
utiliza a extenso ".a" para indicar uma biblioteca esttica, e em Linux elas so criadas atravs do programa
"ar" (archive).

6.3. LINGUAGEM DE MQUINA

81

> ar -rc libMyLib.a a.o b.o c.o d.o


> gcc -o main -lMylib -L.
O programa "ar" simplesmente concatena os arquivos indicados e coloca alguma informao adicional
(para facilitar o acesso s funes) no cabealho do arquivo "libMyLib.a".
A linha de compilao para gerar um aquivo executvel agora um pouco diferente:
-lMyLib : diz ao compilador que o cdigo das funes que no forem encontradas em main.c podem ser encontradas na biblioteca libMyLib.a (observe que so omitidos a extenso (.a) e os caracteres iniciais,
que sempre tem que ser "lib".
-L. : Quando houver necessidade, o compilador (mais especificamente o ligador) ir procurar pela biblioteca
em alguns diretrios padro (/usr/lib, /usr/local/lib, etc), porm no ir procurar no diretrio corrente.
A opo -L" avisa ao ligador para procurar pela biblioteca tambm no diretrio indicado, que no
caso ".", ou seja, o diretrio corrente.
Este mecanismo largamente utilizado e existem mais de 100 bibliotecas (extenso ".a") em /usr/lib.
Cada biblioteca contm uma srie de funes relacionadas. Exemplos incluem libm.a (biblioteca de
funes matemticas), libX.a (X window), libjpeg.a (compresso de imagens para formato jpeg),
libgtk.a (interface grfica), etc.
Cada biblioteca pode ser composta de vrios arquivos objeto, e cada arquivo objeto implementa dezenas
ou centenas de funes, e por vezes torna-se difcil lembrar de todas as funes e da ordem dos parmetros
em cada uma delas. Por isso, para cada arquivo biblioteca est relacionado um arquivo cabealho (header)
e este arquivo deve ser includo para que o compilador possa verificar se os parmetros correspondem aos
parmetros da implementao da funo.
Por vezes difcil lembrar qual o arquivo cabealho correto e qual a biblioteca que deve ser includa
para que um programa possa usar uma determinada funo. Por isso, as man pages relacionam os nomes das
funes, ao arquivo cabealho que deve ser includo e biblioteca desejada.
Tomemos como exemplo a funo sqrt. Quando eu digito man sqrt, aparecem as seguintes informaes:
SQRT(3)

Linux Programmers Manual

SQRT(3)

NAME
sqrt, sqrtf, sqrtl - square root function
SYNOPSIS
#include <math.h>
double sqrt(double x);
float sqrtf(float x);
long double sqrtl(long double x);
Link with -lm.
DESCRIPTION
The sqrt() function returns the non-negative square root of x.
It fails and sets errno to EDOM, if x is negative.
(...)

O cabealho da funo est em <math.h> e tambm apresentado algumas variaes sqrtf e


sqrtl. Com isso, o programador no precisa consultar <math.h> para saber quais e de qual o tipo
so os parmetros. Tambm indica como fazer a ligao (Link with -lm) importante mencionar que
muitas vezes no indicado como fazer a ligao. Nestes casos, a ligao automtica, uma vez que a funo em questo faz parte da libc, biblioteca que usada automaticamente para todos os programas escritos
na linguagem C. Verifique isso consultando as man pages das funes malloc, printf, etc.

82

CAPTULO 6. FORMATOS DE PROGRAMA

Em nosso programa exemplo, a verificao dos parmetros feita na primeira linha do programa, onde
um arquivo com o prottipo da funo inserido. Como no h cdigo associado a esta funo, o compilador
entende que um prottipo e o usa para checar quais os parmetros e o valor de retorno da funo "a".
Para exemplificar a importncia desta linha, compile e execute o programa abaixo, sem a linha #include
(...). Nestes casos, como a informao no foi passada explicitamente pelo programador, o compilador
pressupe que a funo tem um parmetro inteiro e retorna um inteiro. Apesar disso, o ligador ir incluir
normalmente o cdigo da funo e vai acreditar que a checagem de tipos foi feita.
Quando o programa for executado, vrias coisas podem acontecer, inclusive o programa pode gerar o
resultado correto. Porm o mais comum ocorrer algum tipo de erro de execuo. Aqui o erro identificvel
porque o compilador avisa (warning) que falta o prottipo e o que ele est fazendo. Esta "permissividade"
uma das deficincias da linguagem C, apesar de muitos programadores experientes entenderem que isso
uma virtude, pois a torna tima para escovar bits.
#include <stdio.h>
void a (char* s) {
printf ("%s", s);
}

6.3.2.3 Bibliotecas Compartilhadas


Apesar de serem simples de usar e simples de criar, as bibliotecas estticas podem resultar em mau uso da
memria. Para entender por que isso ocorre, basta verificar que existem procedimentos (em especial da libc)
utilizados em praticamente todos os programas. Um exemplo de uma funo com estas caractersticas a
"printf". So poucos os programas que no a utilizam, e se todos estes utilizarem bibliotecas estticas, cada
um deles ter uma cpia desta funo amarrada em seu cdigo executvel. Assim, quando vrios programas
esto na memria, cada um deles tem uma cpia da funo printf. Somando-se todas as cpias, provvel
que alguns megabytes de memria so alocados unicamente para executar somente esta funo.
As bibliotecas compartilhadas permitem que somente uma cpia de cada biblioteca seja colocada na
memria, e permite que todos os programas utlizem aquela cpia como se estivesse dentro do seu prprio
espao de endereamento (ou seja, como se fosse parte do seu programa executvel).
Todos os sistemas operacionais modernos permitem a criao deste tipo de biblioteca, e em algumas
verses a prpria libc includa automaticamente como biblioteca compartilhada.
A seqncia de comandos para gerar uma biblioteca compartilhada para os arquivos objeto "a.c" e "b.c"
a seguinte:
>
>
>
>

gcc -fPIC -c -Wall a.c -o a.o


gcc -fPIC -c -Wall b.c -o b.o
ld -shared -o libMyLib.so a.o b.o
gcc -o main main.c -lMyLib -L.

A opo -fPIC" gera um cdigo objeto relocvel, ou seja, um cdigo cujos endereos so definidos na
hora de carregar o programa na memria. A opo -Wall" pede para sejam impressos todos os avisos de erro
(warnings). A terceira linha quem gera a biblioteca compartilhada com o uso da opo -shared". Observe
que a biblioteca gerada pelo ligador. Finalmente, para gerar o programa executvel, a linha igual quela
apresentada para bibliotecas estticas (seo 6.3.2.2).
Porm, ao executar o programa, recebemos a seguinte mensagem:
> ./main
./main: error while loading shared libraries: libMyLib.so: cannot open
shared object file: No such file or directory
Isto indica que o carregador no conseguiu encontrar a biblioteca compartilhada "libMyLib.so" (o
programa executvel no armazena esta informao). Para corrigir este problema, a soluo mais simples
incluir o caminho da biblioteca "libMyLib.so" dentro da varivel de ambiente "LD_LIBRARY_PATH".

6.3. LINGUAGEM DE MQUINA

83

> echo $LD_LIBRARY_PATH


/public/soft/Linux/avifile/usr/local/lib
> export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:.
> echo $LD_LIBRARY_PATH
/public/soft/Linux/avifile/usr/local/lib:.
> ./main
Hello World
A varivel LD_LIBRARY_PATH indica o diretrio do sistema onde as bibliotecas compartilhadas residem. medida que cada usurio for criando mais bibliocas compartilhadas, o gerenciamento da localizao destas bibliotecas pode se tornar difcil. Por isso, sugere-se que cada usurio tenha um diretrio
"$HOME/usr", com as ramificaes convencionais de "/usr" (bin,lib,include,src, entre outras) e coloque ali
aquilo que for desenvolvendo. Todas as bibliotecas deveriam ser colocadas no mesmo local, e ento basta
colocar em seu ".bashrc" a linha
> export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$HOME/usr/lib
para que todas as bibliotecas sejam includas facilmente. Para o gerenciamento e atualizao de verses das
bibliotecas, mais simples utilizar Makefile nos diretrios /usr/src/... convenientes.
Em tempo de execuo, o carregador coloca o programa a ser executado na memria e liga o programa
a todas as biblitotecas compartilhadas. Todas as biblitotecas devero estar na memria antes do incio da
execuo do programa.
O texto acima somente um resumo desenvolvido para que cada um possa desenvolver uma biblioteca
compartilhada, porm h muitos outros aspectos importantes, como por exemplo controle de verso. Estes
aspectos so apresentados em maiores detalhes na internet1 .

6.3.2.4 Bibliotecas Dinmicas


Apesar de economizarem memria quando comparadas com as bibliotecas estticas, pode ocorrer que algumas bibliotecas compartilhadas sejam includas no espao de memria virtual de um processo, mas que
sejam pouco utilizadas ao longo da execuo. Isto tambm acarreta gasto desnecessrio de memria.
Em ambientes onde o recurso memria muito escasso (como por exemplo em uma servidora que
executa centenas ou milhares de processos), pode ser interessante exigir que somente as bibliotecas que
sero efetivamente usadas sejam carregadas na memria, e que isto ocorra de acordo com as necessidades
impostas pelo programador. Por exemplo, se o programa precisar de uma biblioteca no incio e no fim da
execuo do programa, razovel carreg-la no incio, liber-la durante a execuo do programa e talvez
re-carreg-la ao final para terminar o programa.
As bibliotecas que se comportam desta forma so chamadas de bibliotecas dinmicas (Dynamic Link
Libraries - DLLs), e para us-las necessrio um esforo maior do programador, uma vez que o programa
principal deve sofrer alteraes significativas. Como habitual, vamos exemplificar para uso na linguagem C
em Linux.
1. Deve ser includo o cabealho <dlfcn.h>, que contm os prottipos das funes dlopen, dlclose e
dlsym.
2. A biblioteca deve ser aberta explicitamente atravs da funo dlopen, que ir retornar um handle para
esta biblioteca:
(...)
void *handle;
char *pathLib="/home/.../libMyDynamicLib.so";
(...)
handle = dlopen ( pathLib, RTLD_LAZY );
(...)
1 Em especial[Whea] Alis, este link de leitura obrigatria, ou seja, quando eu elaborar a prova, considerarei que *todos* leram e entenderam o texto. Apesar do texto ser bastante didtico, eventuais dvidas
podem ser levantadas em sala ou por e-mail.

CAPTULO 6. FORMATOS DE PROGRAMA

84

3. A funo desejada na biblioteca deve ser associada a um apontador para ponteiro da seguinte forma:
(...)
void (*localSym)(parametros da funcao na bib);
(...)
localSym = dlsym (handle, "nome da funcao na bib");
4. O smbolo localSym agora um sinnimo da funo indicada na biblioteca. Para executar a funo
da biblioteca, basta usar localSym com os mesmos parmetros da funo.
localSym ( parmetros ... );
5. Ao terminar de usar a biblioteca, deve-se fech-la com a funo dlclose:
dlclose ( handle );
Suponha que temos dois arquivos fonte (a.c e b.c) com os quais deve ser criada a biblioteca libMyDynamicLib.so. Para criar a biblioteca e para gerar o programa executvel, deve ser digitada a seguinte
seqncia de comandos:
>
>
>
>

gcc -fPIC -o a.o -c a.c


gcc -fPIC -o b.o -c b.c
ld -shared -o libMyDynamic.so a.o b.o
gcc -o main main.c -L. -lMyDynamic -ldl

Observe que a nica diferena com relao ao processo de compilao de bibliotecas compartilhadas
a incluso da biblioteca -ldl", que contm as funes "dl*".
Este tpico est bem documentado em dois artigos disponveis na internet, em especial em [Whea, Che]
(que devem ser lidos).

#include <stdio.h>

2
3
4
5

void a (char* s) {
printf ("(a): %s\n", s);
}
Algoritmo 39: Arquivo a.c

#include <stdio.h>

2
3
4
5

void b (char* s) {
printf ("(b): %s\n", s);
}
Algoritmo 40: Arquivo b.c

Como exemplo, considere os algoritmos 39, 40, e 41.


O lao contido entre as linhas 13 e 16 espera que o usurio digite a letra a ou b. As linhas 18 e 19
colocam no vetor s o caminho completo (desde a raiz do sistema de arquivos) para o arquivo onde est a
biblioteca que iremos incluir dinamicamente. Para tal utilizada a funo getenv (get environment variable),
que coloca um ponteiro com o string da varivel de ambiente HOME na varivel local path. Mais adiante
veremos que esta varivel de ambiente (na verdade todas as variveis de ambiente) est inserida no arquivo
executvel.
As linhas 21 at 26 abrem a biblioteca dinmica e verificam se houve algum erro (por exemplo, biblioteca no encontrada). Se tudo correr bem, a varivel error ser NULL, ou seja, zero. A opo RTLD_LAZY

6.3. LINGUAGEM DE MQUINA

1
2

#include <stdlib.h>
#include <dlfcn.h>

3
4
5
6
7
8
9
10
11

int main ( int argc, char** argv ){


char opcao;
void* handle;
void* (*funcao)(char*);
char* error;
char* nomeBib="/lixo/libMyDynamicLib.so";
char* path;
char s[100];

12

do{
printf("Digite (a) para uma bib e (b) para a outra \n");
scanf ("%c", &opcao );
} while (opcao!=a && opcao!=b);

13
14
15
16
17

path = getenv ("HOME");


sprintf(s, "%s%s", path, nomeBib);

18
19
20

handle = dlopen(s, RTLD_LAZY);


error = dlerror();
if ( error ){
printf("Erro ao abrir %s", s );
exit (1);
}

21
22
23
24
25
26
27

if ( opcao == a )
funcao = dlsym(handle, "a");
else
funcao = dlsym(handle, "b");

28
29
30
31
32

funcao ("texto\n");

33
34

dlclose (handle);

35
36

}
Algoritmo 41: Arquivo main.c

85

86

CAPTULO 6. FORMATOS DE PROGRAMA

pode ser entendida como indicador para que a biblioteca seja carregada preguiosamente, ou seja, quando
alguma das funes contidas nela for utilizada. Observe que a biblioteca aberta e associada a uma varivel
(handle). Daquele ponto em diante, todas as vezes que for utilizada a varivel handle, estaremos fazendo
referncia ao arquivo biblioteca aberto.
As linhas 28 at 31 indicam qual das duas funes contida na biblioteca deve ser utilizada. O comando
funcao = dlsym(handle, "a"); pode ser lida da seguinte forma: Associe a funo a() contida na
biblioteca dinmica indicada em handle varivel funcao. Isto se faz basicamente copiando o endereo
da funo a para a varivel funcao.
A execuo est na linha 33. Observe que aqui o contedo da varivel funcao ou o endereo da
funo a ou o endereo da funo b. Como as duas funes tem a mesma seqncia de parmetros, no
ocorrer nenhum problema.
Por fim, a linha 35 fecha a biblioteca.
A forma de gerar o programa executvel, e o resultado obtido pela execuo do programa a seguinte:
> gcc -fPIC -o a.o -c a.c
> gcc -fPIC -o b.o -c b.c
> ld -shared -o libMyDynamicLib.so a.o b.o
> gcc -o main main.c -L. -ldl
> ./main
Digite (a) para uma bib e (b) para a outra
a
(a): texto
> ./main
Digite (a) para uma bib e (b) para a outra
b
(b): texto
>
Como pode ser visto, a gerao da biblioteca (ld ...) igual ao que ocorre em bibliotecas compartilhadas. Porm, h algo de novo na linha gcc -o main main.c -L. -ldl. A opo -ldl, diz para
incluir estaticamente a biblioteca libdl.a. Esta biblioteca que contm as funes dlopen, dlclose,
dlsym e dlerror. Ela contm muitas outras funes do tipo dl*, mas o nosso exemplo s usou estas.
Para maiores detalhes de uso de bibliotecas, consulte[Che, Whea]. A terceira parte do presente texto
mostra como estas bibliotecas se comportam em tempo de execuo.

6.4 O Programa Ligador


At o momento, consideramos o uso de programa ligador em vrias formas. Agora vamos explicar o que ele
faz. Em linhas gerais um programa ligador recebe como entrada uma srie de arquivos executveis e gera
um nico arquivo como sada. Este arquivo executvel.
Porm, por questes didticas, vamos dividir o tema. A seo 6.4.1 trata da ligao de arquivos objeto
com uma nica seo, enquanto que a seo 6.4.2 trata de arquivos objeto com vrias sees. A seo 6.4.3
apresenta alguns detalhes que tambm so relevantes ao processo, e a seo 6.4.4 fecha a seo mostrando
como os conceitos apresentados se unem na prtica.
Estes primeiros aspectos so direcionados para explicar como o ligador trabalha com arquivos objetos
ligados estaticamente. As sees 6.4.5 e 6.4.6 estendem a explicao como o ligador trabalha com arquivos
objetos compartilhados e arquivos objeto dinmicos. Daqui para frente, usaremos os termos objeto esttico,
compartilhado e dinmico para referenciar inclusive as bibliotecas deste tipo (que nada mais so do que uma
coleo de arquivos objeto).
As sees descrevem o tema so fortemente baseados em [Lev00]. Para maiores detalhes, consulte o
livro (que tem uma verso gratuita disponvel na internet, fornecida pelo prprio autor).

6.4.1 Arquivos objeto com um nico segmento


Considere uma situao simples, onde um conjunto de arquivos objeto O1, O2, . . . On, devem ser ligados
para gerar uma arquivo executvel. Cada arquivo contm um nico segmento (seo texto, por exemplo),

6.4. O PROGRAMA LIGADOR

87

Figura 6.1: Concatenao de arquivos objeto de um nico segmento para a composio


de um arquivo executvel
Arquivo
O1
O2
O3
O4

Tamanho
1017
615
920
1390
Tabela 6.1: Arquivos objeto e seus tamanhos

cujos endereos variam de acordo com o tamanho do arquivo. Se o primeiro arquivo tiver T 1 bytes, ento os
endereos deste arquivo variam de 0 at T 1 1. Os endereos do aquivo objeto O2 variam de 0 at T 2 1
e assim por diante.
Em nosso modelo simplificado, o arquivo executvel E seria composto pela concatenao simples dos
arquivos O1, O2, . . ., On. conforme descrito na figura 6.1.

Vamos agora inserir alguns detalhes a mais neste exemplo. Considere que temos exatamente quatro
arquivos objeto: O1, O2, O3 e O4, cada um deles com o tamanho descrito na tabela 6.1.

Se o arquivo executvel iniciar no endereo 0x1000, ento a concatenao dos arquivos ser como
descrito na tabela 6.2. importante observar que este exemplo pressupe um alinhamento de quatro em
quatro bytes no arquivo executvel. Por isso, os bytes dos endereos 2017 e 2F4D, 2F4E, 2F4F esto vagos.

Vamos analisar mais cuidadosamente este exemplo, em especial para esclarecer alguns aspectos sobre
a relocao de endereos. Para tal, vamos aumentar a quantidade de detalhes do exemplo da seo anterior.
Vamos supor que:
1. o arquivo O1 corresponde ao arquivo que contm a funo main;

CAPTULO 6. FORMATOS DE PROGRAMA

88
Arquivo
O1
O2
O3
O4

Endereos
1000-2016
2018-2937
2938-2F4C
2F50-42DF
Tabela 6.2: Composio do arquivo executvel E

Figura 6.2: Relocao de endereos de procedimentos

2. cada um dos arquivos objeto restantes tem um nico procedimento, digamos P2, P3, P4;
3. o arquivo O1 chama os procedimentos P2, P3, P4.
A figura 6.2 ilustra esta situao. Os arquivos objeto esto representados na coluna da esquerda, enquanto que o arquivo executvel representado direita. O arquivo objeto O1 contm as chamadas aos
procedimentos P1, P2 e P3. Fica claro no exemplo que os endereos dos procedimentos no podem ser
atribudos neste arquivo, e por isso foi anotada somente uma referncia aos respectivos nomes.
Os arquivos objeto contm, cada um, a implementao de um destes procedimentos. Este o quadro
antes da gerao do arquivo executvel. Aps passar pelo ligador, o arquivo executvel gerado. Neste
arquivo, as referncias aos procedimeentos foram substitudas pelos endereos deles dentro do arquivo executvel (aqui representados esquematicamente por setas ao invs dos endereos efetivos).

Vamos agora analisar como isto ocorre no mundo real. Uma chamada de procedimento essencialmente
uma instruo call <endereo>. Porm, como o arquivo O1 chama trs procedimentos cujos endereos
no so conhecidos antes de lig-lo com os outros arquivos objeto, o endereo do procedimento chamado
dentro do arquivo O1 fica indeterminado (por exemplo colocando call 0x00000000 no arquivo objeto).
Somente quando os arquivos objeto forem concatenados no arquivo E que o endereo dos procedimentos
ser conhecido, e cada um instruo call 0x00000000 ser substituda por call <endereo do
procedimento em E>.

6.4. O PROGRAMA LIGADOR

89

Observe que o ligador tem de saber qual o endereo que deve ser colocado em cada uma das vrias
chamadas, e por isso o arquivo objeto armazena esta informao em uma tabela de endereos relocveis.
Esta tabela pode ser vista atravs do programa objdump:
> objdump O1.o -r
O1.o:

file format elf32-i386

RELOCATION RECORDS FOR [.text]:


OFFSET
TYPE
VALUE
00000012 R_386_PC32
P2
00000017 R_386_PC32
P3
0000001c R_386_PC32
P4
A primeira coluna (OFFSET) indica o local dentro do segmento texto que devem ser substitudos. A
segunda coluna (TYPE) diz quantos bytes devem ser substitudos. Neste caso, o tipo R_386_PC32 corresponde a quatro bytes2 . Finalmente a terceira coluna indica o smbolo (o nome do objeto no programa) cujo
endereo no arquivo executvel deve ser substitudo. Assim, quando o programa executvel for construdo,
o ligador deve substituir o contedo do endereo 0x00000012 do segmento text pelo endereo efetivo do
procedimento P2 dentro de E e o mesmo para P3 e P4.
Para completar a explicao, usaremos o programa objdump para mostrar os endereos efetivos gerados
pelo nosso exemplo.
Primeiramente, o arquivo O1.
> objdump O1.o -S
O1.o:

file format elf32-i386

Disassembly of section .text:


00000000
(...)
0: 8d
4: 83
7: ff
a: 55
b: 89
d: 51
e: 83
11: e8
16: e8
1b: e8
20: 83
23: 59
24: 5d
25: 8d
28: c3
>

<main>:
4c 24 04
e4 f0
71 fc
e5
ec
fc
fc
fc
c4

04
ff ff ff
ff ff ff
ff ff ff
04

61 fc

lea
and
pushl
push
mov
push
sub
call
call
call
add
pop
pop
lea
ret

0x4(%esp),%ecx
$0xfffffff0,%esp
0xfffffffc(%ecx)
%ebp
%esp,%ebp
%ecx
$0x4,%esp
12 <main+0x12>
17 <main+0x17>
1c <main+0x1c>
$0x4,%esp
%ecx
%ebp
0xfffffffc(%ecx),%esp

A opo -S faz o disassembly, ou seja, l o segmento text e mostra as instrues assembly que
correspondem aos cdigos l inseridos.
Observe as linhas que contm a instruo call. Todas contm o mesmo padro hexadecimal 0xe8fcffffff,
e correspondem instruo call <endereo a ser relocado>. A instruo assembly associada
2 A primeira impresso que todos os endereos relocveis seriam de quatro bytes, porm existem outros
objetos relocveis como por exemplo variveis globais (entre outros) tambm so relocveis. Por isso, foram
criados identificadores de tipo e tamanho para serem usado em cada caso.

90

CAPTULO 6. FORMATOS DE PROGRAMA

call <deslocamento a partir de main>, que facilita encontrar o smbolo associado se usarmos o objdump -r.
Vamos agora analisar o arquivo executvel.
> objdump E -S
(...)
8048354 <main>:
(...)
8048365: e8 16 00 00 00
804836a: e8 1d 00 00 00
804836f: e8 24 00 00 00
(...)

call
call
call

8048380 <P2>
804838c <P3>
8048398 <P4>

08048380 <P2>:
(...)
0804838c <P3>:
(...)
08048398 <P4>:
(...)
>
Como o resultado da operao objdump E -S grande, nos concentramos em mostrar somente a
parte que nos interessa para o exemplo em questo.
Observe agora as instrues call. Cada uma delas foi substituda pelo endereo efetivo dos procedimentos dentro do arquivo executvel. Assim, a chamada P2() contida no arquivo O1, que era associada
instruo assembly call 12 <main+0x12> em O1.o foi agora associada instruo call 8048380
<P2>, ou seja, para o endereo onde o cdigo do procedimento P2 est mapeado dentro de E. Este endereo
corresponde ao endereo virtual quando E for posto em execuo.
Isto nos apresenta outro detalhe interessante. No arquivo objeto, estes endereos so calculados relativo
ao endereo de main, enquanto que no arquivo executvel o endereo o virtual, que ser utilizado ao longo
da execuo do programa. Verifique isso ao simular um programa, por exemplo com o ald.

6.4.2 Arquivos objeto com mais de um segmento


Na seo anterior, apresentamos um exemplo simples, onde vrios arquivos objeto tinham somente o segmento texto e eram concatenados para gerar um arquivo executvel. Na prtica, porm, esta no uma
situao comum. Normalmente os arquivos objeto e os executveis so compostos de vrios segmentos.
O ligador deve concatenar os segmentos equivalentes de cada arquivo objeto em um nico segmento
no arquivo executvel. Um exemplo simples, porm bastante significativo apresentado na figura 6.3, onde
os quatro arquivos objeto tem exatamente dois segmentos. Cada segmento concatenado a seus pares no
arquivo executvel. Assim, todos os segmentos texto so concatenados juntos, enquanto que todos os segmentos de dados tambm o so. As regras de relocao e alinhamento de bytes apresentadas na seo anterior
tambm se aplicam.

6.4.3 Detalhes Importantes


Alm do que j foi descrito acima, os ligadores tambm podem atuar para tornar o programa executvel mais
gil em funo da arquitetura onde o programa dever ser executado. evidente que para tal, eles devem
conhecer algo sobre esta arquitetura (por exemplo, CPU, modelo de paginao, tamanho da palavra, etc.). A
forma de se explorar estes aspectos est normalmente relacionada com o fato de ser o ligador quem define
os endereos virtuais dos segmentos (e de todos os rtulos) que sero usados em tempo de execuo.

6.4. O PROGRAMA LIGADOR

91

Figura 6.3: Arquivos objeto com mais de uma seo e sua relao com o arquivo
executvel
O primeiro detalhe j foi apresentado, que o alinhamento de bytes. Em arquiteturas cujo tamanho da
palavra de 32 bits (ou seja, quatro bytes), razovel iniciar um segmento sempre em um endereo mltiplo
de quatro bytes. Uma das vantagens que desta forma possvel carregar todos os quatro primeiros bytes
do segmento para a CPU em uma nica leitura. Existem outras vantagens, mas esto mais relacionadas com
arquitetura de computadores e no iremos descrev-las aqui.
Outro detalhe se o sistema operacional utiliza paginao. Neste caso, razovel atribuir endereos de
segmento de tal forma que cada segmento resida em uma pgina diferente. Por exemplo, se a pgina de
4K, ento a seo de cdigo pode ser iniciada em um endereo pr-determinado, mltiplo de 4K enquanto
que a seo de dados comearia 4K acima deste endereo (neste exemplo, a seo de texto deve ser menor
do que 4K). Porm quando o tamanho da seo de texto e de dados somados forem menores ou iguais a 4K,
ento razovel coloc-los juntos na mesma pgina.

6.4.4 Exemplo Prtico


As sees anteriores mostraram como funciona o ligador de forma conceitual, e em especial as alteraes
que ele aplica aos arquivos objeto para gerar um arquivo executvel.
Para fechar o assunto sobre o trabalho dos ligadores, vamos detalhar estas alteraes usando um exemplo
no Linux, com arquivos objeto e executvel no formato ELF.
Estes exemplos se destinam a analisar em mais detalhes os endereos atribudos pelo ligador. Para tal,
vamos estender os programas apresentados na seo 6.4.1. Usaremos ao todo quatro programas descritos nos
algoritmos 42,43, 44, 45. A diferea bsica que os programas desta seo usam quatro variveis globais,
uma declarada em cada um dos quatro arquivos objeto.
Observe tambm como foram declaradas as variveis externas ao arquivo O1.c. Elas foram declaradas
com o comando extern, que basicamente diz que as variveis descritas a seguir foram declaradas em outros
mdulos, e que este modulo no precisa alocar espao para elas. Se esta diretiva for retirada, o arquivo O1.o
ir indicar a necessidade de alocar espao para, por exemplo, G2. Porm, o arquivo O2.o tambm faz isso.
Como resultado, o ligador deve acusar um erro e no ser possvel gerar o arquivo executvel.
Sugerimos que o leitor digite os programas e siga os passos aqui indicados.
O primeiro passo compilar os quatro programas para gerar os arquivos O1.o, O2.o, O3.o, O4.o.
Com estes arquivos, gere o arquivo executvel E.
Reveja a seo 6.4.1 e digite os comandos objdump correspondentes. Analise os valores dos endereos.
Se quiser, retire as referncias s variveis e compare os resultados com os obtidos na seo 6.4.1.

CAPTULO 6. FORMATOS DE PROGRAMA

92

1
2
3

#include <stdio.h>
int G1;
extern int G2, G3, G4;

4
5
6
7
8
9
10

int main () {
G1=15;
P2(); G2=2;
P3(); G3=3;
P4(); G4=4;
}
Algoritmo 42: arquivo O1.c

int G2;

2
3
4
5

int P2 () {
return(1);
}
Algoritmo 43: arquivo O2.c

int G3;

2
3
4
5

int P3 () {
return(2);
}
Algoritmo 44: arquivo O3.c

int G4;

2
3
4
5

int P4 () {
return(3);
}
Algoritmo 45: arquivo O4.c

6.4. O PROGRAMA LIGADOR

93

Execute o E usando um simulador. Observe que os endereos que esto indicados no arquivo executvel
correspondem aos mesmos endereos em tempo de execuo. Isto mostra que o ligador quem define estes
endereos.
Agora estamos prontos para analisar as sees geradas. As sees podem ser vistas com o comando
readelf -a O1. Este programa equivalente ao objdump, porm nos fornece mais informaes sobre
o arquivo.
O resultado deve ser algo semelhante ao seguinte:
> readelf -a O1.o
ELF Header:
Magic:
7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
Class:
ELF32
Data:
2s complement, little endian
Version:
1 (current)
OS/ABI:
UNIX - System V
ABI Version:
0
Type:
REL (Relocatable file)
Machine:
Intel 80386
Version:
0x1
Entry point address:
0x0
Start of program headers:
0 (bytes into file)
Start of section headers:
256 (bytes into file)
Flags:
0x0
Size of this header:
52 (bytes)
Size of program headers:
0 (bytes)
Number of program headers:
0
Size of section headers:
40 (bytes)
Number of section headers:
10
Section header string table index: 7
Section Headers:
[Nr] Name
Type
Addr
Off
Size
ES Flg Lk Inf Al
[ 0]
NULL
00000000 000000 000000 00
0
0 0
[ 1] .text
PROGBITS
00000000 000034 000051 00 AX 0
0 4
[ 2] .rel.text
REL
00000000 0003a0 000038 08
8
1 4
[ 3] .data
PROGBITS
00000000 000088 000000 00 WA 0
0 4
[ 4] .bss
NOBITS
00000000 000088 000000 00 WA 0
0 4
[ 5] .comment
PROGBITS
00000000 000088 00002d 00
0
0 1
[ 6] .note.GNU-stack
PROGBITS
00000000 0000b5 000000 00
0
0 1
[ 7] .shstrtab
STRTAB
00000000 0000b5 000049 00
0
0 1
[ 8] .symtab
SYMTAB
00000000 000290 0000f0 10
9
7 4
[ 9] .strtab
STRTAB
00000000 000380 000020 00
0
0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings)
I (info), L (link order), G (group), x (unknown)
O (extra OS processing required) o (OS specific), p (processor specific)
There are no section groups in this file.
There are no program headers in this file.
Relocation section
Offset
Info
00000013 00000801
0000001c 00000902
00000022 00000a01
0000002b 00000b02

.rel.text at offset 0x3a0 contains 7 entries:


Type
Sym.Value Sym. Name
R_386_32
00000004
G1
R_386_PC32
00000000
P2
R_386_32
00000000
G2
R_386_PC32
00000000
P3

CAPTULO 6. FORMATOS DE PROGRAMA

94
00000031
0000003a
00000040

00000c01 R_386_32
00000d02 R_386_PC32
00000e01 R_386_32

00000000
00000000
00000000

G3
P4
G4

There are no unwind sections in this file.


Symbol table .symtab
Num:
Value Size
0: 00000000
0
1: 00000000
0
2: 00000000
0
3: 00000000
0
4: 00000000
0
5: 00000000
0
6: 00000000
0
7: 00000000
81
8: 00000004
4
9: 00000000
0
10: 00000000
0
11: 00000000
0
12: 00000000
0
13: 00000000
0
14: 00000000
0

contains 15 entries:
Type
Bind
Vis
NOTYPE LOCAL DEFAULT
FILE
LOCAL DEFAULT
SECTION LOCAL DEFAULT
SECTION LOCAL DEFAULT
SECTION LOCAL DEFAULT
SECTION LOCAL DEFAULT
SECTION LOCAL DEFAULT
FUNC
GLOBAL DEFAULT
OBJECT GLOBAL DEFAULT
NOTYPE GLOBAL DEFAULT
NOTYPE GLOBAL DEFAULT
NOTYPE GLOBAL DEFAULT
NOTYPE GLOBAL DEFAULT
NOTYPE GLOBAL DEFAULT
NOTYPE GLOBAL DEFAULT

Ndx
UND
ABS
1
3
4
6
5
1
COM
UND
UND
UND
UND
UND
UND

Name
O1.c

main
G1
P2
G2
P3
G3
P4
G4

No version information found in this file.


>
O programa readelf nos fornece muitas informaes interessantes, mas vamos analisar somente uma
parte delas.
Primeiramente a diviso em sees. Observe mais atentamente a parte Sections Headers:, que
enumera todas as sees contidas no arquivo. Talvez muitos esperassem ver somente duas sees (.text e
.data), porm aps dcadas de experincias, todos os formatos de arquivos objeto e executveis passaram
a acrescentar mais sees, cada uma delas contendo informaes especficas. Estas informaes nem sempre
significam alguma coisa para o programador, porm so extremamente teis para os programas ligador e
carregador.
Vamos agora analisar o tamanho das sees. Observe que a seo .text comea no endereo 0x34
do arquivo3 . Do endereo 0x0 at 0x33 esto contidas informaes sobre a organizao do arquivo. No
endereo 0x34 comeam as informaes sobre a seo .text. Como pode ser visto, a seo .text tem
o tamanho de 0x51 bytes, o que significa que a seo .text est contida entre os bytes 0x34 e 0x84
inclusive (lembre-se que o primeiro byte est no endereo 0x34).
A prxima seo rel.text, texto relocvel. Ela a segunda a ser descrita, mas est posicionada no
endereo 0x3a0 e tem tamanho 0x38. Ela cotm informaes sobre os nomes relocveis, como variveis
globais e funes. A listagem mostra tambm uma descrio sobre o contedo desta seo. Abaixo, uma
cpia daquele trecho.
Relocation section
Offset
Info
00000013 00000801
0000001c 00000902
00000022 00000a01
0000002b 00000b02
00000031 00000c01
0000003a 00000d02
00000040 00000e01

.rel.text at offset 0x3a0 contains 7 entries:


Type
Sym.Value Sym. Name
R_386_32
00000004
G1
R_386_PC32
00000000
P2
R_386_32
00000000
G2
R_386_PC32
00000000
P3
R_386_32
00000000
G3
R_386_PC32
00000000
P4
R_386_32
00000000
G4

3 Considerando que o primeiro byte do arquivo est no endereo 0x0, o segundo byte est no de endereo
0x1, e assim por diante.

6.4. O PROGRAMA LIGADOR

95

So ao todo sete entradas: quatro para as variveis globais e trs para funes. A nica entrada que
tem tamanho especificado G1, que foi declarada neste mdulo (O1.c). As demais entradas no foram
declaradas neste mdulo (Sym.Value=0).
Em seguida, vem a seo .data. Ela deveria comear no byte seguinte ao fim da seo .text, ou seja,
0x85, mas comea no endereo 0x88. Isto ocorre porque esta seo deve comear em um byte mltiplo de
quatro (alinhamento de quatro bytes). Isto significa dizer que no h informao til nos bytes 0x85, 0x86
e 0x87.
Veja que a seo .bss est vazia (tem tamanho zero). A seo .data tambm, o que pode parecer
estranho, pois deveria conter a informao sobre a varivel global G1. Porm como este arquivo est no
formato objeto, no necessrio alocar espao para cada varivel global, basta deixar indicado que o arquivo
executvel deve faz-lo. Por isso, esta informao foi colocada na seo .rel.text (texto relocvel, mas
que aqui seria melhor se fosse traduzida para smbolos relocveis). Veja onde so colocadas as variveis
globais e os nomes de procedimentos (tambm chamados de smbolos) no arquivo executvel. Compare
tambm os tamanhos das sees .data nos arquivos objeto e no arquivo executvel.
Apesar de existirem muitas outras sees, estas no tem interesse para este livro. Para mais informaes,
consulte [Com95].

6.4.5 Ligadores para objetos compartilhados


A seo anterior apresentou o modelo bsico de funcionamento dos ligadores, onde os arquivos objeto so
includos no arquivo executvel. Este modelo corresponde ao que se denomina objetos ligados estaticamente. Este modelo foi o primeiro modelo de ligadores que foram criados, porm apresenta dois problemas
fundamentais:
1. quando so includos muitos arquivos objetos (ou se os arquivos objeto forem grandes), o arquivo
executvel tende a ser muito grande, gastando muito espao em disco.
2. em tempo de execuo possvel que programas diferentes usem memria fsica para armazenar
informaes sobre um mesmo arquivo objeto. Um exemplo clssico o da funo printf em um
ambiente acadmico. Normalmente todos os alunos tendem a usar esta funo, e se por exemplo, 100
alunos estiverem trabalhando em uma mesma mquina, ento provvel que s o cdigo da funo
printf ocupe vrios megabytes de memria fsica.
Estes dois problemas motivaram a busca de alternativas para diminuir o tamanho de arquivos executveis
e do tamanho do programas em execuo.
Atualmente esto disponveis duas alterantivas, utilizar objetos compartilhados e utilizar objetos dinmicos. O primeiro ser abordado nesta seo, enquanto que o segundo ser abortado na prxima seo
(6.4.6.
No caso de objetos compartilhados, o sistema operacional quem gerencia o processo. O usurio deve
criar os auivos objeto compartilhado, e agrup-los em bibliotecas para inclu-los no arquivo executvel.
Porm, diferente do que ocorre com os arquivos objetos estticos, o arquivo objeto no copiado para o
arquivo executvel, mas sim o nome do arquivo que contm o objeto.
Como exemplo, considere que os arquivos objeto usados na seo anterior sejam gerados como arquivos
objeto compartilhados, e com este seja gerado o arquivo executvel:
>
>
>
>
>

gcc -fPIC -c O2-semVars.c -o O2-semVars.o


gcc -fPIC -c O3-semVars.c -o O3-semVars.o
gcc -fPIC -c O4-semVars.c -o O4-semVars.o
ld -shared -o libbibComp.so O2-semVars.o O3-semVars.o
gcc O1-semVars.c -o O1-semVars -lbibComp

O4-semVars.o

Suponha que ainda no ajustamos a varivel de ambiente LD_LIBRARY_PATH, e queremos saber


quais os arquivos (agrupados em bibliotecas) fazem parte deste arquivo executvel. O programa ldd traz
esta informao. Vamos mostrar o efeito de executar este comando antes de atualizar a varivel de ambiente:
> ldd -r O1-semVars
undefined symbol: P2 (./O1-semVars)
undefined symbol: P4 (./O1-semVars)
undefined symbol: P3 (./O1-semVars)

96

CAPTULO 6. FORMATOS DE PROGRAMA

Linux-gate.so.1 => (0xb7f9e000)


libbibComp.so => not found
libc.so.6 => /lib/tls/i686/cmov/libc.so.6 (0xb7e55000)
/lib/ld-Linux.so.2 (0xb7f9f000)
>
Observe os smbolos indefinidos: so as funes referenciadas em O1-semVars. Em seguida, ele apresenta alguns nomes:
Linux-gate.so.1 Esta biblioteca ser associada ao endereo 0xb7f9e000 quando o programa for carregado para a execuo.
libbibComp.so Esta biblioteca no foi encontrada porque no atualizamos a varivel de ambiente.
libc.so.6 Esta biblioteca est em /lib/tls/i686/cmov/libc.so.6, e ser associada ao endereo
0xb7e55000 quando o programa for carregado para a execuo.
/lib/ld-Linux.so.2 a biblioteca que faz a ligao compartilhada em tempo de execuo, e ser associada
ao endereo 0xb7f9f000 em tempo de execuo.
Agora, vamos ver como a referncia a libbibComp.so se comporta quando mudamos a varivel de
ambiente:
> export LD_LIBRARY_PATH=\$LD_LIBRARY_PATH:.
> ldd -r O1-semVars
Linux-gate.so.1 => (0xb7f36000)
libbibComp.so => ./libbibComp.so (0xb7f32000)
libc.so.6 => /lib/tls/i686/cmov/libc.so.6 (0xb7deb000)
/lib/ld-Linux.so.2 (0xb7f37000)
> ldd -r O1-semVars
Linux-gate.so.1 => (0xb7f1e000)
libbibComp.so => ./libbibComp.so (0xb7f1a000)
libc.so.6 => /lib/tls/i686/cmov/libc.so.6 (0xb7dd3000)
/lib/ld-Linux.so.2 (0xb7f1f000)
Observe agora a refercia a libbibComp.so "magicamente" foi ajustada sem mexer no arquivo
executvel.
Como exerccio, faa um hexdump no arquivo O1-semVars. Ver que o arquivo no contm nenhuma informao de onde procurar a biblioteca libbibComp.so. Em tempo de carregamento que esta
biblioteca procurada nos diretrios indicados em LD_LIBRARY_PATH.
Como j foi citado antes, uma das vantagens deste tipo de biblioteca que gera um arquivo executvel
menor. Porm, se compararmos os tamanhos dos arquivos executveis gerado nas sees 6.4.2 e 6.4.5,
veremos que a verso compartilhada maior. Isto ocorreu porque na verso compartilhada foi necessrio
incluir bibliotecas para gerenciar o funcionamento e carregamento das bibliotecas, enquanto que na verso
esttica isso no foi necessrio. Porm, para bibliotecas grandes, a verso compartilhada vera arquivos muito
menores do que a esttica.

6.4.6 Ligadores para objetos dinmicos


O objetivo dos objetos dinmicos fazer um melhor uso da memria. Por vezes, um programa chama uma
biblioteca para executar um pequeno pedao de cdigo durante sua iniciao, e no mais usado posteriormente. Porm, se a biblioteca for gerada estaticamente, o cdigo ser copiado para dentro do arquivo
executvel, e ocupar espao de memria ao longo da execuo do programa. Se a biblioteca for gerada de
forma compartilhada, ento a biblioteca no ocupar espao no arquivo executvel, mas ocupar espao em
tempo de execuo.
Vamos considerar o cenrio ende programa so grandes. Considerando execuo no Linux, assuma
4Gbytes de memria virtual no so suficientes para incluir todo o programa executval. Neste caso, no
possvel colocar o programa todo em execuo. Porm, pode-se usar um artifcio onde se incluem e se
retiram bibluiotecas durante a execuo do programa.

6.5. O PROGRAMA CARREGADOR

97

Existem sistemas operacionais que tem este problema, no usam o modelo de memria virtual, e por
isso dependem da quantidade de memria fsica disponvel para executar.
Nestes casos, uma soluo interessante a de colocar o problema na mo do programador. Se o programa dele no cabe em, por exemplo, 4Gbytes, ele pode selecionar quais as bibliotecas que devem ficar
disponveis a cada momento, e com isso ele consegue calcular qual o tamanho mximo de memria usada
seguindo as vrias possibilidades de alocao das bibliotecas.
Considerando-se os objetos dinmicos, a tarefa do ligador simples se comparada com a tarefa do
carregador. O ligador deve basicamente incluir uma biblioteca esttica que tem os mdulos para carregar e
liberar bibliotecas em tempo de execuo (no caso apresentado, -ldl na linha de compilao/ligao para
incluir a biblioteca libdl.a (veja seo 6.3.2.4). E s.
O resto do trabalho do programa carregador, e da biblioteca libdl.a.

6.5 O Programa Carregador


A funo do programa carregador trazer um programa para a memria e coloc-lo em execuo. O carregador um programa associado ao sistema operacional, e cada sistema operacional trabalha normalmente com
um nico formato de arquivo executvel, apesar de que possvel criar carregadores que podem trabalhar
com mais de um formato (como no caso do Windows para os formatos COM, EXE e PE).
Explicado da maneira mais genrica possvel, o programa carregador l um arquivo que est no formato
executvel, cria um novo processo, aloca espao na memria para este processo, e copia as informaes do
arquivo executvel para a memria virtual.
Algumas observaes importantes:
O programa no obrigatoriamente iniciar a execuo assim que for diponibilizado. Em Linux, por
exemplo, quando um programa colocado para a execuo, ele basicamente colocado na lista
de processos, e o sistema operacional selecionar aquele processo para a execuo de acordo com
alguma poltica do escalonador de processos.
Em princpio, um arquivo executvel gerado para operar em uma determinada arquitetura (CPU+SO),
no pode ser executado em outra arquitetura ou em outro SO. Porm existem formas de fazer isso
atravs de emuladores, interpretadores ou ainda adaptadores. Estas ferramentas sero analisadas na
seorefsec:interpret.
possvel gerara cdigo executvel para uma arquitetura CPU diferente daquela na qual se est
trabalhando.
Existem vrias classes de carregadores. Este texto apresentar trs classes: carregadores que copiam
os programas em memria fsica sem relocao (seo 6.5.1), carregadores que copiam os programas em
memria fsica com relocao (seo 6.5.2), carregadores que copiam os programas em memria virtual
(seo 6.5.3).

6.5.1 Carregadores que copiam os programas em memria fsica


sem relocao
Esta classe de relocador corresponde ao modelo mais simples, e foi vastamente utilizada nos primeiros
programas em PC. O seu membro mais conhecido o carregador que trabalha com o formato COM do
MS-DOS.
Antes de surgir o modelo EXE, este era nico tipo de carregador fornecido pelo MS-DOS, e ele
bastante simples. Quando um programa colocado para a execuo, o carregador l o arquivo que contm
o programa a ser executado. Este arquivo, como j sabemos, deve estar organizado em um formato especial
para que o carregador possa entend-lo.
Assim, quando o carregador confirma que o arquivo em questo est no formato apropriado, ele copia
o arquivo a partir do endereo de memria 0x0100 (na regio da memria entre os endereos 0x0000 e
0x0100, chamada PSP, onde so armazenadas outras informaes, como por exemplo a linha de comando
digitada).
Quando o programa estava totalmente copiado na memria fsica, o controle de execuo era passado
para o endereo 0x0100, ou seja, o programa comeava a execuo.

98

CAPTULO 6. FORMATOS DE PROGRAMA

Figura 6.4: Modelo funcionamento do carregador COM


A figura 6.4 mostra esquematicamente o trabalho do carregador. O exemplo desta figura contm um
programa de 0x0200 bytes no formato COM. Como j foi visto, este programa ser copiado para a memria
a partir do endereo 0x0100. Desta forma, o endereo 0x0000 do arquivo executvel ser copiado para o
endereo 0x0100 da memria fsica e assim por diante at o endereo 0x0200 do arquivo executvel que
ser copiado para o endereo 0x0300 da memria fsica.

Um detalhe importante que est colocado na figura o comando jmp. O parmetro deste comando
o endereo para qual desviar. Este endereo corresponde ao endereo em memria fsica. Este endereo
conhecido pelo ligador porque o ligador sabe que o programa ser carregado a partir do endereo 0x0100.
Sendo assim, o arquivo executvel gerado pelo ligador j contm o endereo correto. Alis, alm de correto,
este endereo imutvel, o que limita sensivelmente este modelo.
Como exemplo, suponha que este programa seja carregado a pertir de outro endereo, digamos 0x1000.
Ento, quando o programa chegar no comando jmp, o fluxo ser desviado para 0x0270, e vai seguir a
execuo a partir daquele ponto (seja l o que ele encontrar).
Aps este exemplo j possvel examinar algumas caractersticas deste modelo:
1. O MS-DOS era um sistema operacional monousurio e monoprocesso. Isto significa dizer que s um
processo podia estar em execuo em cada momento. Outro processo s podia entrar em execuo
se o processo atual fosse cancelado (ou copiado temporariamente para outro local).
2. O carregador COM no fazia relocao de endereos. Como o ligador sabe que este ser o nico
processo a ser executado e que ele comea sempre no endereo 0x0100, ento os endereos de
todos os objetos (aqueles normalmente associados a rtulos, como endereos de desvio, variveis e
procedimentos) j podiam ser calculados em tempo de ligao.
3. Em verses posteriores do MS-DOS, tentou-se criar uma maneira de colocar mais de um processo
em execuo. O mecanismo consistia em chamar o sistema operacional quando determinadas teclas
fossem acionadas pelo usurio (por exemplo, ALT e TAB) para que toda a imagem do processo em
execuo fosse copiado para uma rea de salvamento em disco, e que outro processo fosse colocado
em execuo em seu lugar. Quando o usurio quisesse colocar o outro processo de volta para a
execuo, bastava usar as duas teclas mgicas para que a imagem do processo atual fosse salvo
em disco e que a imagem salva do outro processo fosse copiado novamente para a memria. Com
isso, criava-se a iluso de um ambiente multiprocessos onde o mecanismo de troca de processos era
gerenciado pelo usurio.

6.5. O PROGRAMA CARREGADOR

99

4. Para que um processo pudesse ser colocado em execuo, deveria haver memria fsica suficiente
para comport-lo. Curiosamente, esta primeira verso do MS-DOS trabalhava com um dos primeiros processadores intel x86, onde podia-se enderear at 64Kbytes. Sendo assim, nenhum programa
executvel podia ser maior do que 64K. Posteriormente este limitao caiu, porm algumas caractersticas importantes (como tamanho do segmento de 64k) continuaram por ainda alguma tempo.
O formato COM o mais simples de todos os formatos apresentados neste texto. Alguns aspectos
dele foram projetados para trabalhar no computador IBM-PC, e baseiam-se em vrias caractersticas de
hardware, em especial do processador intel. A melhoria dos processadores permitiram melhorias no modelo
de execuo. Para maiores referncias a estas melhorias, consulte [Paz07]

6.5.2 Carregadores que copiam os programas em memria fsica


com relocao
evidente que as deficincias no modelo COM eram graves, e algumas tinham razes no prprio processador intel da poca (que eram basicamente de 8 bits). Quando evoluiram para processadores de 16 bits, a
intel encontrou uma forma de enderear at 20 bits usando registradores de segmento, o que permitiu um
endereamento total de at 1Mbytes de memria fsica.
Com esta melhoria do hardware, era necessria uma melhoria do sistema operacional tambm, especialmente uma melhoria no modelo no formato de execuo. O modelo COM, monoprocesso, continuou
existindo, porm a Microsoft criou o modelo EXE (que tambm bastante semelhante ao modelo de mesmo
nome do CP/M). Este modelo debutou no MS-DOS 2.0.
Este modelo usa vrias caractersticas interessantes, algumas destas caracterstics baseavam-se em aspectos do hardware, e fogem ao escopo da discusso. Porm uma caracterstica importante foi a de usar
uma tabela de relocao, que apresentava os endereos de todos os smbolos que deveriam ser relocados
(alterados) quando o programa fosse colocado em execuo.
O carregador lia o arquivo executvel, guardava os endereos de relocao e ento colocava o programa
para executar em qualquer endereo fsico disponvel, digamos 0x0700. Todos os endereos reloveis eram
procurados na memria e substitudos pelos novos endereos relativos a 0x0700. Quando este procedimento
terminava, o programa podia ser disparado a partir do endereo 0x0700, pois todos os endereos relocveis
j foram ajustados.
Este modelo permitiu um sistama realmente multiprocessos, porm a questo de gerenciamento de
procesos e de memria ainda era bastante rudimentar.
Uma outra caracterstica importante desta classe de carregadores que toda a rea de memria era
considerada rea vlida para execuo. O usurio que, sem querer (ou por querer) tinha acesso a locais
onde hoje se prega que no devem ter acesso, como ao sistema operacional, sendo que poderiam alter-lo.
Um exemplo a quantidade de livros que foram lanados na poca explicando como alterar os destinos das
interrupes contidas no vetor de interrupes.
O comprometimento da segurana permitiu o rpido ataque de diversos programas mal-intencionados (e
por vezes divertidos), chamados vrus. Quando o MS-DOS foi estendido para uso em redes de computadores,
foi possvel escrever vrus que se reproduziam em todos os computadores da rede.
Porm, tambm importante destacar que o MS-DOS foi projetado para operar em um computador, com
um nico processo para um nico usurio. Neste ambiente, ao acreditar que o usurio bem intencionado
(afinal, porque criar um vrus para atacar o prprio computador?), no h motivos para preocupaes muito
grandes. Porm, quando este sistema foi estendido para multiusurios e para redes de computadores, o tmido
modelo de segurana simplesmente ruiu. Desde esta ruptura, o nmero de vrus aumentou significativamente,
sempre atacando falhas deixadas a partir da prpria concepo do sistema operacional.
Apesar de Microsoft alegar que as verses atuais de seus sistemas operacionais so mais resistentes a
falhas e vrus, a quantidade de vrus detectados semanalmente mostram que estas falhas ainda existem.

6.5.3 Carregadores que copiam os programas em memria virtual


Como foi visto nas duas sees anteriores, o mapeamento de um programa diretamente em memria fsica pode ocasionar problemas. O principal destes o acesso a toda a memria fsica atravs de qualquer
programa.

CAPTULO 6. FORMATOS DE PROGRAMA

100

O modelo de memria virtual soluciona este problema, uma vez que cada programa recebe uma grande
quantidade de memria virtual linear para trabalhar (uma grande caixa de areia onde ele pode fazer o que
quiser). Pedaos desta memria virtual so mapeados para a memria fsica, e o cada processo s tem acesso
s suas pginas fsicas. O gerenciamento de memria virtual feito diretamente em hardware e pelo sistema
operacional fazendo com que o mapeamento seja transparente para o usurio e rpido. Evitentemente no
to rpido quanto os modelos anteriores, uma vez que o tempo de carregar novas pginas da memria virtual
para a fsica pode gerar pequenos atrasos.
Aqui, o carregador deve copiar o programa contido no arquivo para o espeo de memria virtual alocado
para este programa. Depois de copiar todas as sees (text, data, bss, etc.), basta habilitar o programa para a
execuo.
Aqui tambm existe relocao, porm ela depende do tipo de biblioteca (esttica, compartilhada e dinmica) esto contidos no arquivo executvel. Trataremos cada caso em uma seo diferente.

6.5.3.1 Executveis com Bibliotecas Estticas


Quando as bibliotecas so estticas, o ligador gera endereos virtuais fixos, sem relocao. A tarefa do
carregador basicamente copiar as sees para a memria virtual. bastante semelhante ao relocador
do modelo COM, porm como trabalha-se com memria virtual e no com memria fsica, no tem os
inconvenientes daquele modelo.

6.5.3.2 Executveis com Bibliotecas Compartilhadas


Quando um arquivo executvel faz referncia a bibliotecas compartilhadas, o carregador tem de fazer muito
trabalho.
Depois de copiar o arquivo executvel para a rea de memria virtual daquele processo, o carregador
copia as bibliotecas compartilhadas para outra rea da memria virtual. Aqui importante observar que
somente depois disto que o carregador sabe quais so os endereos relocveis (pois s agora sabemos quais
os endereos virtuais de cada objeto relocvel).
O problema do carregador ento passa a ser encontrar todos os endereos contidos no arquivo carregado
que exigem relocao, para ento atribuir a eles os endereos corretos.
Para facilitar esta tarefa, o ligador cria uma seo contendo todos os objetos do arquivo executvel que
devem ser relocados. Existem vrias formas de se fazer a ligao entre o arquivo executvel e as bibliotecas
que ele usa. A idia mais simples (e ineficiente) listar todas os enderos que devem ser relocados e esperar
que o carregador determine quais os endereos corretos em tempo de execuo, substitundo cada uma das
entradas. Isso causa algumas complicaes relacionadas com endereamento, e por isso no adotada.
Quando se fala somente em desvios, uma soluo mais inteligente usar uma tabela de desvios, onde
cada entrada corresponde a um smbolo. Assim, cada procedimento a ser relocado tem uma entrada, e cada
entrada tem um pequeno trecho de cdigo. O programa executvel desvia para esta entrada e esta entrada
desviar para o destino apropriado. Neste caso, o carregador s precisa relocar o destino deste trecho de
cdigo, deixando todo o resto do cdigo intacto.
Esta idia implementada em vrios modelos de execuo, inclusive no ELF, onde a tabela chamada
plt4 . Assim, ao invs de o arquivo executvel conter chamadas aos procedimentos que sero ligados em
tempo de execuo, ele chama os procedimentos contidos na seo .plt, que por sua vez ir desviar o fluxo
para as bibliotecas compartilhadas que forem carregadas. A vantagem deste mtodo que no necessrio
substituir todos os destinos das chamadas de procedimento que esto contidas no arquivo executvel. Nenhuma alterada na seo texto, e o carregador deve somente alterar os endereos contidos na seo plt.
Desta forma, se um programa fizer duzentas chamadas ao procedimento P1, ento o carregador no precisa
alterar o endereo destas duzentas chamadas, uma vez que elas desviam para uma entrada na seo plt.
Basta ao carregador alterar a entrada da seo plt para o destino correto.
Uma entrada simplificada na seo plt apresentada a seguir para o programa O1(seo 6.4.4).
> objdump -S O1
(...)
Disassembly of section .plt:
(...)
4 Procedure

Linkage Table.

6.5. O PROGRAMA CARREGADOR

101

080483b0
80483b0:
80483b6:
80483bb:

<P4@plt>:
ff 25 a4 96 04 08
68 18 00 00 00
e9 b0 ff ff ff

jmp
push
jmp

*0x80496a4
$0x18
8048370 <_init+0x18>

080483c0
80483c0:
80483c6:
80483cb:

<P2@plt>:
ff 25 a8 96 04 08
68 20 00 00 00
e9 a0 ff ff ff

jmp
push
jmp

*0x80496a8
$0x20
8048370 <_init+0x18>

call
movl

80483b0 <P4@plt>
$0x4,0x80496bc

Disassembly of section .text:


080483d0 <_start>:
(...)
80484ad: e8 fe fe ff ff
80484b2: c7 05 bc 96 04 08 04
80484b9: 00 00 00

H pelo menos dois aspectos a serem analisados. Primeiro, observe que a chamada ao procedimento P4
(call 80483b0 <P4@plt>). A referncia P4plt, ou seja, o procedimento P4 que est na seo plt.
O endereo do procedimento (0x80483b0) est listado um pouco acima. Isto significa que o programa
executvel contm um desvio para P4, porm no aquele que ser ligado em tempo de execuo, mas um
outro, na seo plt, que ser futuramente alterado para desviar para o procedimento da biblioteca dinmica
carregada.
O segundo aspecto corresponde ao formato do endereo de desvio jmp *0x80496a4. Este modo de
endereamento correponde a desvio indireto, ou seja, o desvio para o endereo contido em 0x80496a4, e
no para 0x80496a4. A tarefa do carregador colocar o endereo correto de P4 no endereo 0x80496a4.

6.5.3.3 Executveis com Bibliotecas Dinmicas

6.5.4 Emuladores e Interpretadores.


Emuladores e interpretadores so termos que muitas vezes so usados
Emulador O termo emulador est associado a sistemas de hardware ou de software bsico. Um emulador
capaz de duplicar o comportamento das funes de um sistema A em um sistema B de tal forma
que o sistema B se comporte exatamente como se fosse o sistema A.
Interpretador Um termo interpretador normalmente associado a linguagens de programao. Um interpretador um sistema capaz de executar instrues de uma linguagem interpretada.
Desta forma, existem linguagens de programao interpretadas e emuladores de hardware. A seo 6.5.4.1 detalha como funcionam os emuladores enquanto que a seo 6.5.4.2 detalha como funcionam
os interpretatores.

6.5.4.1 Emuladores
O termo emulador foi cunhado para indicar um hardware que se comporta como outro. Porm, com o tempo,
o termo foi estendido para descrever sistemas de sofware bsicos tambm. Um exemplo interessante o
wine5 (Wine Is Not Emulator - um emulador de aplicaes windows para linux).
Este programa l um arquivo executvel no formato usado pelo windows (COM, EXE, PE) e converte
para o formato ELF. Aps esta converso, o programa colocado em execuo em linux. A idia simples:
a seo de intrues do formato origem copiada para seo text do ELF. A seo de dados globais
copiada para a seo data, e assim por diante. Com isso, o formato original totalmente convertido para
ELF e o programa pode executar.
Porm, ainda resta um problema. Em tempo de execuo, um programa pode (e deve) usar chamadas ao
sistema. O programa origem foi projetado para fazer uso das chamadas de sistema da Win32 (o equivalente
5 http://www.winehq.org

CAPTULO 6. FORMATOS DE PROGRAMA

102

ao POSIX do linux), e da mesma forma: indica parmetros nos registradores e chama a instruo int
0x???? (os endereos so diferentes do linux).
Assim, a grande tarefa do wine no o de copiar o arquivo windows para o formato executvel ELF, mas
de projetar chamadas de sistema equivalentes ao Win32 para linux. A idia trocar chamadas de sistema por
chamadas a procedimentos. Se necessrio, estas chamadas de procedimento usam as chamadas de sistema
nativas do linux.
A verso da Win32 que foi projetada para o wine no uma cpia da win32, mas sim uma verso livre
baseada em engenharia reversa. Para programas bem comportados (ou seja, os que s acessam o hardware
atravs das chamadas ao sistema), ele funciona muito bem. Porm para programas que acessam o hardware
diretamente, no funciona, evidentemente.
O grande problema como converter grficos complexos (por exemplo simulaes e jogos) do windows
para linux, e a idia natural usar uma bibliteca grfica para fazer o trabalho. Porm, como as bibliotecas
grficas da Microsoft (DirectX) e do linux (OpenGL) no so totalmente compatveis, os grficos podem ter
de ser emulados, gerando perda de desempenho.

6.5.4.2 Interpretadores
Um interpretador um programa que interpreta instrues. Para tal, ele deve ser invocado e deve ser dito
para ele qual o programa que ele deve executar.
Como exemplo, considere um interpretador de uma linguagem assembly, o SPIM. Este interpretador
simula o comportamento de um programa assembly escrito em MIPS durante sua execuo.
O interpretador teria vrias estruturas de dados, das quais destacamos duas:
Vetor para representar os registradores, digamos REGS, onde o ndice representa o nmero do registrador. Assim, o comando REGS[2]=12, insere o valor decimal 12 no registrador 1 (chamado
at).
Vetor para representar a memria, digamos MEM. Cada posio deste vetor corresponde ao valor
contido em uma posio de memria. Nem todo o vetor precisa ser iniciado. Como o SPIM interpreta
um programa que usa memria virtual (ou seja, d a iluso de que s um processo usa a memria),
ento o vetor MEM teria, em princpio, 4G bytes. Porm, o programador no precisa alocar tudo
isso, uma vez que cada programa interpretado usa nem 1% em mdia.

1
2
3
4
5
6

.data
.text
.globl main
main:
li $v0, 10
syscall
Algoritmo 46: Programa Spim

O programa 46 equivalente ao programa 2, com a diferena que no coloca nada em $).


A semelhana com os programas assembly que vimos aqui visvel. O camando li $v0, 10 armazena a constante 10 no registrador $v0.
Para simular a execuo deste programa, o interpretador l uma linha por vez. Na primeira linha,
encontra .data, e para cada nome ali declarado, sabe que dever alocar um espao na memria (vetor
MEM). A segunda linha indica que inicia a parte de cdigo e logo abaixo o rtulo main.
Quando finalmente encontra a intruo li $v0, 10, o interpretador inicia o trabalho de interpretar a
instruo.
Primeiro, divide a instruo em trs partes: Cdigo de operao (li (load immediate), operando 1
($v0) e operando 2 (10). Lembre-se que o registrador $v0 corresponde ao registrador nmero 2, ou seja, o
operando 1 corresponde ao decimal 2.
Em seguida, executa um trecho de cdigo semelhante ao descrito no programa 2.
Observe que o programa a ser executado residir na seo .data, enquanto que o interpretador residir
na seo .text.

6.5. O PROGRAMA CARREGADOR

1
2
3
4
5
6
7
8
9
10

103

...
switch (CodigoDeOperacao)
case codigo_de_mv :
interpreta comando move
break;
...
case codigo_de_li :
REGS[OP1]=OP2
break;
...
Algoritmo 47: Trecho do Interpretador

O interpretador pode alocar variveis, chamar procedimentos e qualquer outra coisa que a linguagem
especificar. Por exemplo, ao ler uma intruo que diz para colocar zero em uma varivel, o interpretador ir
procurar esta varivel em sua tabela de variveis e ao encontr-la, atribuir zero a ela. Se a varivel no foi
declarada no programa assembly, ser mostrado um erro.
Porm, este um caso especfico do interpretador SPIM, pois praticamente todas as linguagens interpretadas permitem alocao de variveis em tempo de execuo (sem necessidade de declarao). Assim, se
a varivel Z recebe o valor zero, e no foi declarada, o interpretador a insere em sua tabela de variveis
e atribui zero a ela. Apesar de conveniente, isso j causou srios problemas, pois se o programador errar e
digitar X ao invs de Z, a varivel X ser declarada e coexitir com Z. O problema como detectar
este erro.
Aqui importante destacar a linguagem Java. Ela uma linguagem de programao criada pela SUN
para orientao a objetos baseada fortemente na sintaxe da linguagem C. Um compilador Java capaz de
traduzir a linguagem Java para um assembly particular chamado bytecode. A peculiaridade do bytecode
que ele um assembly para uma CPU inexistente. O cdigo executvel segue o modelo ELF apresentado
aqui com vrias sees (.text, .data, etc.). A diferena que a seo .text tem bytecodes ao invs de
intrues assembly nativas.
A grande sacada que a SUN tambm projetou interpretadores para este formato executvel. Estes interpretadores vm em vrios sabores: windows, linux e mac. Com isso, a linguagem Java compilada por
um compilador Java que gera um arquivo executvel para bytecodes. O interpretador l este arquivo executvel e pode executar o mesmo cdigo em vrios sistemas diferentes. Como o arquivo executvel independe
da arquitetura em que foi gerado (ou seja, os compiladores java geram o mesmo arquivo executvel para
um mesmo arquivo fonte Java), este arquivo executvel multiplataforma. Um mesmo arquivo executvel
Java pode ser executado (interpretado) em windows, linux e mac sem alteraes!
Para executar aplicaes java, necessrio ter instalado o Ambiente de Tempo de Execuo Java (ou
Java Runtime Environment (JRE)). Este ambiente composto por bibliotecas (APIs) e pela mquina virtual
java (JVM).

104

CAPTULO 6. FORMATOS DE PROGRAMA

Apndice A

Formatos de Instruo
Este apndice aborda alguns dos formatos de instruo nos processadores da famlia x861 . Com formato de
instruo, entende-se a forma com que a instruo dividida para conter o cdigo de operao e os operandos
(se houverem).
Este tpico mais simples quando analisamos processadores RISC (reduced instruction set computer) ,
onde cdigo de operao e operandos esto organizados de maneira bastante regular em dois ou trs formatos
diferentes. Nestas mquinas, o cdigo de operao tem um tamanho fixo, e ocupa o mesmo espao em qualquer dos formatos. J em processadores CISC (complex instruction set computer), no h esta regularidade,
e os cdigos de operao podem ocupar de poucos a muitos bits e vrios formatos de instruo so adotados,
o que torna difcil determinar quando termina o cdigo de operao e quando comeam os operandos.
Esta um dos aspectos que fazem com que os processadores RISC sejam considerados melhores do
que os computadores CISC (apesar de isso nem sempre ser verdade). Nas mquinas CISC, como as instrues aparecem em grande nmero e so complexas, a unidade de controle deve ser maior para contemplar
todas as regras e todas as excesses dos vrios formatos. A CPU deve ficar maior, e por conseqncia as
distncias entre os componentes internos ficam maiores tambm, fazendo com que os dados demorem mais
para trafegar entre os componentes (por exemplo, entre registradores). O efeito da implementao direta
de um processador CISC que as suas intrues ficam mais lentas.
Por outro lado, a regularidade das instrues RISC faz com que a unidade de controle seja mais simples,
e conseqentemente menor. Com isso, a CPU pode ser projetada de forma mais simples e as distncias entre
os componentes tambm ficam menores. A conseqncia que as instrues demoram menos tempo para
serem completadas, e o efeito que elas passam a ser mais rpidas.
Os processadores da famlia x86 so exemplos de processadores CISC e honram a classe. Eles utilizam
vrios modos de endereamento e vrios tamanhos para o cdigo de operao.
Aqui o leitor deve estar se perguntando: ento porque ainda insistimos com processadores CISC (em
especial da famlia x86) e no nos convertemos para processadores RISC? A resposta o preo. Cada novo
processador da famlia x86 executa o conjunto de instrues de seus predecessores de forma mais rpida
que geraes anteriores (maravilhas da engenharia), acresentam algumas instrues novas de acordo com as
necessidades de mercado (como por exemplo MMX e SSI) e com isso tem mercado praticamente garantido
para venda. Com a massificao, os custos de projeto e de produo so minimizados (para no dizer
pulverizados). J os processadores RISC nem sempre tem mercado garantido (pelo menos no na magnitude
dos x86). Com isso, tanto os custos de projeto quanto os de produo podem ser sentidos no preo final do
processador.
1 Originalmente estes processadores foram desenvolvidos pela Intel. Exemplos destes processadores:
80186, 80286, 80386, ... importante destacar que a AMD fez engenharia reversa destes processadores
e desenvolveu processadores que atuam com o mesmo conjunto de instrues. Assim, do ponto de vista do
usurio, os processadores x86 da Intel e da AMD tem o mesmo comportamento, e funcionam igualmente
para o mesmo conjunto de programas.

105

APNDICE A. FORMATOS DE INSTRUO

106

A.1 Modos de Endereamento


Os operandos de uma instruo em assembly podem variar de acordo com o local em que o dado se encontra.
Como exemplo, observe a diferena entre as instrues:
movl %eax, %ebx (endereamento registrador)
movl $0, %ebx (endereamento imediato)
movl a, %ebx (endereamento direto)
movl %eax, (%ebx) (endereamento indireto)
movl a(,%edi,4), %ebx. (endereamento indexado)
Estes so os modos de endereamento que esta seo abordar para processadores da famlia x86 (e mais
freqentemente encontrados em processadores modernos), e esta seo detalha o formato de cada modo de
endereamento nos x86.
Existem outros modos de endereamento no descritos acima porque eles no foram implementados
nesta famlia de processadores, como por exemplo os que lidam com dois operandos em memria. Eles no
so descritos aqui porque alm de eles no terem sido implementados nesta famlia de processadores eles
esto praticamente em desuso nos processadores modernos.
Antas de iniciar a explicao, existem dois aspectos que queremos destacar:
1. Nos exemplos acima foi usada somente a instruo movl. Apesar de ser uma nica instruo, ela foi
projetada para conter um nmero variado de operandos. Cada um destes modos de endereamento
da instruo movl tem um cdigo de operao diferente, o que os diferencia internamente para o
processador. Observe agora que possvel identificar cada modo de endereamento a partir da forma
com que se escreve a instruo. Esta forma de escrever a instruo estendida para praticamente
todas as instrues aritmticas e lgicas, ou seja, compreendendo a forma de escrever o modo de
endereamento registrador com a instruo movl, tambm se compreende a foram de endereamento
registrador para a instruo addl.
2. A nomenclatura dos modos de endereamento (Registrador, Imediato, Direto, Indireto e Indexado)
no nica. Os manuais da Intel usam uma taxonomia um pouco diferente: imediato, registrador,
memria (que aqui est dividido em base/deslocamento e base/ndice/deslocamento) e portas de I/O
(que no ser coberto aqui). Para maiores detalhes veja [Int04a, pp 3-20].

A.2 Registrador
Este modo de endereamento envolve dois operandos que so registradores.
Um exemplo simples a instruo movl %eax, %ebx, onde o contedo do registrador %eax ser
copiado2 para %ebx. A instruo contm trs informaes: cdigo de operao, operando 1 (%eax) operando 2 (%ebx). O modo de endereamento est implcito no cdigo de operao (ou seja, o cdigo de
operao neste caso : copie o valor de %eax para %ebx usando endereamento registrador). Para outros
modos de endereamento, o cdigo de operao diferente.
Os detalhes de endereamento da instruo movl pode ser encontrada em [Int04b], no comando mov/move.
Em um arquivo executvel, esta instruo ser associada aos smbolos hexadecimais 89 C3 (escreva
um programa assembly que contm esta instruo e veja), ou em binrio 1000 1001 1100 0011 .
Nestes bits esto armazenados em trs campos: cdigo de operao (movl), operando destino (%eax)
e operando origem (%ebx). Infelizmente, os formatos de instruo da do x86 no so regulares, e por isso
o nmero de bits usado para cada um destes campos em uma instruo no igual ao nmero de bits usado
em outras instrues.
Nesta instruo particular, o cdigo de operao ocupa 10 bits, o operando destino ocupa 3 bits e o
operando origem tambm ocupa trs bits.
Se separarmos os bits da instruo para melhor visualizao dos trs campos, teremos o seguinte:
2 Observe que apesar da instruo ser move (mover em portugus), a operao de cpia. Como diria
Joo Grilo (O Auto da Compadecida), No sei porque, s sei que assim.

A.3. IMEDIATO
Registrador
eax
ecx
edx
ebx
esp
ebp
ebi
edi

107

Nmero
000
001
010
011
100
101
110
111

Tabela A.1: Nmeros internos associados aos registradores x86

1000100111 000 011

{z

} |{z} |{z}

CO

Op1 Op2

Onde CO=Cdigo de Operao, Op1=Operando 1, Op2=Operando 2. Observe que o registrador %eax


(000)2 = 010 , enquanto que o registrador %ebx (011)2 = 310 . Seria natural esperar que o registrador
ebx fosse o de nmero 1, porm a relao diferente, como pode ser visto na tabela A.1.

A.3 Imediato
Este o modo de endereamento que envolve uma constante (tambm chamada de valor imediato) que est
includa na prpria instruo. Nos x86, possvel codificar constantes de 8, 16 e 32 bits.
Como exemplo, considere a instruo movl $1, %eax, cuja representao em hexadecimal b8 01 00 00 00 00
(seis bytes). Em binrio, temos:
1011 1 000 0000 0001 0000 0000 0000 0000 0000 0000
{z
}
|{z} |{z} |
|{z}
CO

OP 1

Imediato

O cdigo da instruo ocupa os primeiros quatro bits. O bit seguinte um modificador (w) que indica que a instruo opera sobre uma palavra (32 bits). Os trs bits seguintes indicam o registrador destino
((000)2 =%eax), enquanto que os ltimos 32 bits armazenam a constante. Lembre que os x86 so little
endian3 por isso os bytes aperecem invertidos. Como exemplo veja que a constante 0x12345678 armazenada como 78 56 34 12 .

A.4 Endereamento Direto


No endereamento direto, a instruo contm o endereo do dado a ser utilizado, como por exemplo na
instruo movl a, %eax, onde a um rtulo na seo data (lembre-se que os endereos dos rtulos so
definidos em tempo de ligao). Assumindo que o endereo do rtulo a 0x8049098, ento a instruo
ser codificada como a1 98 90 04 08 , ou em binrio:
1010 0001 1001 1000 1001 0000 0000 0100 0000 1000
{z
}
|{z}
|
CO

Op1

3 Segundo a webopedia, os termos big endian e little endian tem origem no livro As viagens de Gulliver.
Este livro descreve um conflito poltico entre os liliputianos: se ovos cozidos deveriam ser abertos no lado
maior (Big Endian) ou no lado menor (Little Endian). De maneira anloga, o debate em computao est
tambm mais relacionado com problemas polticos do que com mritos tecnolgicos segundo a mesma fonte.

APNDICE A. FORMATOS DE INSTRUO

108

Se alterssemos a instruo para movl a, %ebx, teramos o seguinte padro 8b 1d 9c 90 04 08 .


interessante observar que esta instruo completamente diferente da anterior, pois o cdigo de operao
daquela instruo se aplica somente para mover um dado para o registrador %eax, enquanto o cdigo de
operao desta instruo se aplica a qualquer um dos outros registradores, e para tal, um dos operandos indica qual dos registradores est sendo envolvido. A esta altura, o leitor j deve ter observado, mas no custa
nada observar mais uma vez que instrues especficas como esta so muito comuns em arquiteturas CISC,
e tornam o seu conjunto de intrues mais complexo, maior e irregular.

A.5 Endereamento Indireto


No modo de endereamento indireto, um dos parmetros contm uma referncia indireta memria. Como
exemplo, considere as seguintes instrues:
movl $a, %ebx
movl (%ebx), %eax
A primeira instruo coloca em %ebx o endereo do rtulo a (sem o $, seria colocado o contedo do
endereo de a). A segunda instruo coloca em %eax o valor do endereo indicado por %ebx. Estas duas
instrues so uma verso mais complicada para implementar a instruo movl $a, %eax.
A instruo movl (%ebx), %eax codificada em hexadecimal como 8b 03 , e em binrio, temos:
10001011 00

000 011
| {z } |{z} |{z}
|{z}
CO

OP 1 OP 2

Observe que alguns bits esto amarrados em zero (os indicados com ?). Eles no fazem parte do
cdigo de operao, e talvez no devessem estar ali. Porm, esto talvez s para preencher o nmero de bits
necessrios para completar dois bytes.

A.6 Endereamento Indexado


O que aqui chamamos de endereamento indexado tambm conhecido como endereamento de memria
base e deslocamento.
No endereamento indexado, a instruo usa um endereo base e um deslocamento. Um exemplo
a instruo movl a(,%edi, 4), %ebx, que usa a como base e %edi 4 como deslocamento. O
endereo efetivo &a + %edi 4, (onde &a indica o endereo de a) e o contedo deste endereo
movido para %ebx. Uma outra forma de ver o funcionamento deste tipo de endereamento descrito no
algoritmo abaixo.

1
2
3
4

movl $a, %eax


muli 4,%edi
addl %edi, %eax
movl (%eax), %ebx
Algoritmo 48: Instruo movl a(,%edi, 4), %ebx

Observe que o segundo parmetro (o registrador %edi) multiplicado pelo terceiro parmetro. Isto
usado quando os elementos do vetor esto distantes quatro bytes (por exemplo, porque so valores inteiros).
Se fosse um vetor de bytes, a instruo seria movl a(,%edi, 1), %ebx. Ou seja, o terceiro parmetro
indica o tamanho do tipo de dados contido no vetor.
Agora, vamos analisar o formato desta instruo. Assim como no exemplo anterior, se assumirmos que
o endereo de a 0x80490a0, obteremos a seguinte instruo em hexa: 8b 1c bd a0 90 04 08 ,
onde:

A.6. ENDEREAMENTO INDEXADO

109

10001011 00 01 1 100 1011 1101 . . .

| {z } |{z} |{z} |{z} | {z } |{z}


CO

%ebx

end.a

Os trs primeiros bytes armazenam o cdigo de operao, o registrador destino, o registrador a ser
acrescentado (no caso, %edi e o multiplicador 4). Os ltimos bytes contm o endereo de a.
Como pode ser visto com estes exemplos, os modos de endereamento dos processadores da famlia
x86 no so regulares, e existem excesses situaes que as tornam ainda mais irregulares (como acrescentar
bits amarrados em zero para completar um byte).
Este um dos motivos que normalmente se evita utilizar esta arquitetura nas disciplinas de arquitetura
de computadores em cursos de graduao.

110

APNDICE A. FORMATOS DE INSTRUO

Apndice B

MMX
Esta seo trata de processamento paralelo. Apresentaremos a abordagem que a Intel adotou para inserir
processamento paralelo nos processadores da famlia x86.
O processamento paralelo uma das alternativas mais interessantes para aumentar a velocidade de um
processador. Existem vrias abordagens para tal, e uma delas a SIMD1 , onde uma instruo pode efetuar
operaes sobre vrios dados ao mesmo tempo. O projeto de uma CPU com processamento paralelo algo
muito caro e normalmente para um mercado especfico. Assim, para manter o mercado dos processadores
da famlia x86, e melhorar o desempenho dos processadores desta famlia, a Intel optou por acrescentar
um coproessador (oito registradores de 64 bits e 57 instrues que operam sobre sobre registradores). Isto
pode parecer estranho (um coprocessador embutido dentro de outra CPU), porm isso bastante comum.
Antigamente existiam coprocessadores aritmticos (e espao para eles na placa-me), porm atualmente a
esmagadora maioria dos coprocessadores est embutida dentro da CPU principal (que normalmente lida s
com inteiros). muito mais simples (e economicamente vivel) criar um coprocessador a um processador
de sucesso do que criar um processador novo, mas talvez sem mercado.
Este coprocessador foi projetado para melhorar o desempenho de aplicaes multimdia (como por
exemplo software interativo em tempo real2 ) e comunicaes. Este apndice ir explicar resumidamente
como desenvolver aplicaes em assembly para este coprocessador, porm o enfoque explicar como funciona processamento paralelo e por isso o coprocessador MMX ser utilizado como o exemplo de aplicao
deste conceito. O texto serve como um ponto de partida para o leitor desenvolver aplicaes MMX, porm
no tem a inteno de ser completo.
Este apndice est dividida em duas partes: A seo B.1 explica como verificar se o processador contm
MMX e a seo B.2 apresenta uma aplicao simples (soma um a cada elemento de um vetor de inteiros).

B.1 Como verificar a presena do MMX


Os manuais da intel[Int04a, Int04b, Int04c] apresentam o cdigo abaixo para verificar a presena ou no do
coprecessador MMX no processador. importante que o programador implemente uma alternativa para o
caso do processador no estar presente, porm a nossa implementao simplesmente cancela o programa se
o MMX no for detectado.
O algoritmo 49 foi projetado para ser chamado a partir de um programa em C, e por isso primeiramente
ele termina de alocar o registro de ativao. Isto pode at ser desnecessrio, porm adotamos esta soluo
para manter um padro. O teste da presena de MMX feito pela instruo CPUID, que insere 1 no bit 23
de %edx. A instruo test compara o padro $0x00800000 (bit 23 aceso e todos os demais apagados)
com o registrador %edx. Se o resultado for zero, o bit 23 de %edx est apagado (MMX ausente), e se for
1, o bit 23 de %edx est aceso (MMX presente). Ao final, o registro de ativao liberado. O retorno da
funo 1 se MMX estiver presente e zero caso contrrio.
1 Single
2 mais

Instruction Stream Multiple Data Stream


conhecidos como joguinhos.

111

112

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

APNDICE B. MMX

procura_MMX:
# termina de alocar Reg.Ativao
pushl %ebp
movl %esp, %ebp
mov $1, %eax
CPUID
# Analisa bit 23 de %edx. Se estiver ligado, a CPU tem MMX.
test $0x00800000, %edx
jnz MMX_Presente
mov $0, %eax # MMX ausente. Retorna zero.
jmp fim
MMX_Presente:
mov $1, %eax # MMX presente. Retorna um.
fim:
# liberacao de Reg.Ativao
movl %ebp, %esp
popl %ebp
ret
Algoritmo 49: Verifica presena de MMX

B.2 Exemplo de aplicao paralela


Esta seo exemplifica o uso do coprocessador MMX, de seus registradores e instrues. Para tal, utilizada
uma aplicao simples: somar um a vrios bytes consecutivos armazendos em memria.
Esta aplicao no foi projetada para ter uma finalizade prtica, porm pode ser utilizada em processamento de imagens. Nesta rea, cada pixel de uma imagem em tons de cinza normalmente representado
por um byte (enquanto que em imagens coloridas so usados mais bytes). Um vetor de unsigned int
armazena todos os pixels. Se a imagem tiver X pixels de largura por Y pixels de altura, ento o vetor deve
ter espao para pelo menos X Y bytes.
Cada byte do vetor corresponde a um pixel. Assim, o byte 1 corresponde ao pixel do canto superior
esquerdo da imagem. Se o byte tiver o valor 0, ento o pixel preto. Se o byte tive o valor 255 (o maior
inteiro sem sinal que pode ser representado em oito bits), o pixel branco. Quanto maior o valor, mais claro
o pixel.
Suponha que desejamos somar um a cada um dos bytes do vetor. A primeira soluo, a mais direta,
implementar um lao que l um byte de cada vez, coloca-o em um registrador, soma um a este registrador e
armazena o byte de volta no vetor.
Apesar de funcionar, esta soluo lenta pois o processador fica parado a maior parte do tempo, uma
vez que o acesso memria relativamente mais lento do que o acesso a registradores. Alm disso, o
barramento entre o processador e a memria capaz de transportar 32 bits por vez, e somente oito esto
sendo transportados a cada iterao do ciclo.
Uma soluo mais prtica trazer quatro pixels por vez para a CPU (32 bits), o que usa o mximo
possvel do barramento. O problema agora separ-los em bytes e som-los individualmente.
Como este tipo de aplicao comum, a tecnologia MMX permite que uma operao (como por exemplo somar um) seja efetuada em paralelo em todos os quatro bytes de um registrador de 32 bits. Um
registrador MMX contm 32 bits e pode ser somado byte a byte com um outro registrador. A soma de um
byte no afeta a soma do byte vizinho.
Com MMX, o algoritmo passa a carregar os quatro bytes de uma imagem em um registrador MMX.
Um outro regitrador mmx recebe o valor $0x01010101 (que so quatro bytes com os valores 1) e os
dois registradores so somados. O resultado colocado de volta no vetor. No necessrio pensar muito
para perceber que este algoritmo praticamente quatro vezes mais rpido do que o anterior. O algo-

B.2. EXEMPLO DE APLICAO PARALELA

113

ritmo 50, e a funo somaIm pode ser invocada de um programa em C com o seguinte prottipo: somaIm
(endInicioVetor, tamVetor).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

somaIm:
# termina de alocar Reg.Ativao
pushl %ebp
movl %esp, %ebp
movl 8(%ebp), %eax
movl 12(%ebp), %edx
addl %eax, %edx
movl $0x01010101, %ebx # quero somar um a cada um dos bytes.
movd %ebx, %mm1
loop:
movl (%eax), %ecx
movq (%eax), %mm0
paddusb %mm1, %mm0
movq %mm0, (%eax)
addl $4, %eax
cmpl %eax, %edx
jge loop
fimSomaIm:
# inicia liberacao de Reg.Ativao
movl %ebp, %esp
popl %ebp
ret
Algoritmo 50: Soma um a um vetor

As linhs 3-4 completam o registro de ativao e as linhas 21-22 liberam o registro de ativao. Lembrese: esta funo foi projetada para ser usada a partir de um programa em C.
Na linha 5, o registrador %eax recebe o endereo de incio do vetor (que o parmetro armazendo em
8(%ebp)). Na linha 6, o registrador %edx recebe o tamanho do vetor (que o parmetro armazendo em
12(%ebp)). A linha 7 calcula o endereo do fim do vetor e armazena-o em %edx.
A cada iterao do lao, o valor de %eax somado de um. Quando o valor de %eax for igual ao valor
de %edx, o lao termina.
Na linha 9, a constante $0x01010101 colocada em %ebx e depois em %mm1. Este valor corresponde
a quatro bytes iguais a um. A idia carregar um conjunto de quatro bytes por vez da memria e som-los
em paralelo com %mm1. Ao final da soma em paralelo, o resultado ser que os quatro bytes lidos tero sido
somados de um3 .
O lao ocorre entre as linhas 10-17. Para colocar os primeiros quatro bytes do vetor em %mm0, usamos o
registrador %ecx como intermedirio (observe que %eax indica o endereo do prximo conjunto de quatro
bytes a ser somado).
A soma em paralela executada pela instruo paddusb %mm0, %mm1, e o resultado da soma
colocado direto na memriamovq %mm0, (%eax). A execuo desta instruo est demonstrada esquematicamente na figura B.2.
Para exemplificar o que ocorre, suponha que $mm0 = 0x00010203 e que $mm1 = 0x01010101
(que o valor que foi colocado l, mas que o leitor pode modificar como quiser). Ento, a instruo
3 pode ser que tenha um jeito mais simples de colocar uma constante em um registrador mmx, porm eu
s encontrei a instruo movq. Se voc encontrar uma alternativa, me avise

114

APNDICE B. MMX

Figura B.1: Soma paralela em registradores MMX.

paddusb %mm0, %mm1 realizar as seguintes somas em paralelo: 00+01, 01+01, 02+01, 03+01,
resultando em $mm0 = 0x01020304.
A seqncia de somas paralelas continua em cada nova iterao. O lao prossegue at o fim do vetor
(ou seja, quando %eax %edx (lembre-se que %edx indica o endereo de fim do vetor.
O exemplo apresentado usa somente a instruo que soma quatro bytes em paralelo (quatro grupos de
oito bits), porm existem outras instrues paralelas, e no s para grupos de oito bits. Tambm foram
projetadas instrues para somar grupos de dezesseis bits e extenses para registradores de 64 bits. O elenco
completo de instrues est disponvel disponvel nos manuais dos processadores. Existem tambm vrios
stios na internet dedicados programao usando mmx.

Apndice C

Memria Virtual
possvel que programas sejam maiores do que a memria fsica. Por exemplo, um programa que precise
de 2Gbytes em um computador que s tem 1Gbytes de memria fsica. Este problema no novo, e nos
ltimos 50 anos, vrias solues foram tentadas.
Este captulo apresenta duas solues para resolver este problema. Existem vrias outras solues que
fogem ao escopo deste texto.
As duas tcnicas apresentadas so aquelas que so de alguma forma utilizadas em outras partes do texto.
A primeira soluo, apresentada na seo C.1 chamada overlay. A segunda soluo apresentada na
seo C.2 chamada Paginao.

C.1 Overlay
Um exemplo de soluo o uso de overlays. A idia dividir o programa em pedaos, chamados overlays,
e carregar cada um deles na memria individualmente, excluindo outros se for o caso.
Para dar uma idia melhor de como funciona esta tcnica, descrevo uma situao que era bastante comum na dcada de 80. Eu era programador de uma empresa, e os computadores da poca tinham muita
pouca memria (quando comparado com os atuais), que variavam entre 128Kbytes e 512Kbytes. As aplicaes mais comuns eram aquelas que disponibilizavam trs opes para o usurio: : incluso, alterao e
excluso.
O usurio que usava este tipo de programa sempre estava em uma das seguintes situaes:
1. na tela que pergunta qual das trs operaes o usurio quer executar,
2. nas telas referentes incluso,
3. nas telas referentes alterao,
4. nas telas referentes excluso.
O importante a ser observado que cada situao exclui as demais, ou seja, quando o usurio estiver na
parte de incluso, ele no usar o cdigo referente alterao e nem excluso.
O mesmo ocorre quando estiver em alterao (que no precisa de incluso e nem de excluso) ou excluso (que no precisa de incluso e nem de alterao).
Para dar um toque mais palpvel a este cenrio, considere a figura C.1. Do lado esquerdo da figura,
apresentamos o programa indicado. O programa composto pelos mdulos de Incluso, Alterao, Excluso
e a tela que pede a opo do usurio.
A ttulo de exemplo, considere que o mdulo de incluso ocupa 100 bytes, o mdulo de alterao ocupa
110 bytes, o mdulo de excluso ocupa 60 bytes e as opes ocupam 90 bytes.
Do lado direito da figura, vemos que a memria fsica tem 150 bytes. Porm, o programa todo precisaria
de 100 + 110 + 60 + 90 = 360 bytes. Em outras palavras, este programa no cabe na memria.

115

APNDICE C. MEMRIA VIRTUAL

116

Figura C.1: Esquerda: programa com vrias opes. Direita:Memria Fsica

Porm uma anlise mais cuidadosa mostra que a incluso, que precisa de 110 bytes, cabe sozinha na
memria. O mesmo ocorre com a alterao, com a excluso e com as opes. Alis, os mdulos de excluso
e de opes cabem juntos.
Como era comum este tipo de situao (programas maiores do que a memria), as linguagens de programao muitas vezes tinham diretivas adicionais para que fossem definidos os overlays.
A idia era escrever, em alguma parte do cdigo, algo como BEGIN_OVERLAY e END_OVERLAY
para dizer ao compilador quais trechos de cdigo correspondiam a cada overlay.
Desta forma era o programador quem especificava cada overlay. No era muito divertido quando um
overlay no cabia na memria. Era necessio compactar o cdigo de alguma forma, como por exemplo
trocando seqncias de cdigo longas, porm claras, por seqncias equivalentes mas pequenas, porm nada
claras.
A idia desta soluo basicamente empurrar o problema para o programador. Ele que deveria separar
o programa em overlays. Porm, foi uma soluo muito utilizada (em especial por este que escreve o texto).
Apesar de parecer arcaica e sem propsito nos dias atuais, esta soluo muito parecida com a forma
de se gerenciar bibliotecas dinmicas, onde o programador quem diz quais so as funes que devem ser
mapeadas na memria a cada passo. Em vrios casos, o programador capaz, inclusive de saber qual o uso de
memria no pior caso (ou seja, quando objetos dinmicos est presente na memria fsica simultaneamente
ocupam o maior espao).

C.2 Paginao
A paginao a forma que grande parte dos sistemas operacionais modernos usam para fazer com que
programas maiores do que o total de memria fsica possam ser executados.
Um dos maiores atrativos para o uso deste mecanismo que ele no precisa ser gerenciado pelo programador. Quem gerencia o processo o Sistema Operacional, ajudado por uma ferramenta de hardware
chamada MMU (Memory Management Unit).

Considere a figura C.2. Esta figura descreve um sistema onde a memria virtual permite um espao
de endereamento de 64K bytes, enquanto que a memria fsica tem somente 32K bytes. O espao de
endereamento virtual pode conter qualquer coisa, e no contexto deste texto, contm um processo (a caixa

C.2. PAGINAO

117

Figura C.2: Modelo de Memria Virtual

APNDICE C. MEMRIA VIRTUAL

118

de areia na qual um processo pode brincar). Um processo no pode ser executado a partir da memria
virtual, ento, necessrio copi-lo primeiro para a memria fsica para somente ento poder execut-lo.
O lado esquerdo da figura C.2 apresenta o espao de endereamento virtual, enquanto que o lado direito
apresenta o espao de endereamento real. Tanto um quanto outro esto divididos em blocos de 4Kbytes, chamados pginas. So 16 pginas para endereas virtuais (pginas virtuais 0 at 15) e 8 pginas de
endereos fsicos (pginas reais 0 at 7).
Agora, vamos analisar o que ocorre quando necessrio acessar endereos virtuais. Suponha que o
espao de endereamento virtual contm um programa em execuo (um processo). Como j conhecido,
quando um programa executado, ele acessa instrues que esto na memria fsica. Como cada pgina
virtual composta por 4Kbytes, ela capaz de conter vrias instrues. A ttulo de exemplo, considere
que a execuo do processo exige a leitura de instrues que esto na pgina virtual 7. O processo executa
vrias instrues nesta pgina e ento acessa a pgina virtual 8. Esta pgina contm instrues que acessam
variveis globais (que esto na pgina virtual 5). Em seguida, faz uma chamada de procedimento. Este
procedimento est na pgina 9 e os seus parmetros esto na pgina virtual 2. Ao terminar o procedimento,
o processo volta para a pgina virtual 8 e finaliza a sua execuo.
Ao longo de toda a vida do processo, ele acessou as seguintes pginas virtuais: 7,8,5,9,2,7.
Sabendo agora quais pginas virtuais foram acessadas, vamos abordar como feito o mapeamento entre
pginas virtuais e fsicas.
Quando um processo inicia a execuo, iniciada uma tabela de mapeamento de pginas virtuais para
pginas fsicas. Inicialmente, esta tabela vazia, como mostrado na tabela C.1.

Pgina Virtual
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

Pgina Fsica
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X

Tabela C.1: Tabela de mapeamento de endereos virtuais para endereos fsicos: situao inicial

A tabela composta por 16 linhas (pois so 16 pginas virtuais). Todas as pginas inicialmente esto
marcadas com X, pois no esto mapeadas em nenhuma pgina fsica.
A primeira pgina virtual acessada a pgina 7. Como esta pgina no est mapeada em memria fsica,
ocorre uma armadilha (trap) chamada page fault. O nome rmadilha apropriado. A idia que se algum
tentar acessar algo que no pode, a armadilha chama o sistema operacional para gerenciar o acesso. Neste
momento, o sistema operacional pode verificar se o usurio pode acessar aquela pgina ou no. Quando no
pode, normalmente o programa abortado com a simptica mensagem segmentation fault. Quando pode,

C.2. PAGINAO

119

o sistema operacional escolhe uma pgina fsica para onde copiar a pgina virtual. Vrios critrios de escolha
podem ser usados, mas o mais comum escolher a pgina fsica que est h mais tempo sem acessos.
Suponha que a pgina fsica que o sistema operacional escolheu foi a pgina fsica 2. Com isso, nova
tabela de mapeamento alterada, como indicado na tabela C.2. Nesta nova tabela, somente a entrada 7 foi
alterada.

Pgina Virtual
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

Pgina Fsica
X
X
X
X
X
X
2
X
X
X
X
X
X
X
X

Tabela C.2: Tabela de mapeamento de endereos virtuais para endereos fsicos: Aps
acesso pgina virtual 7

Isto implica dizer que o processo acredita que est usando a pgina virtual 7, mas na realidade esta
pgina est mapeado na pgina real 2, ou seja, ele est usando a pgina real 2. importante observar que
se este processo for colocado em execuo novamente, a pgina virtual 7 pode ser mapeada em outra pgina
fsica.
Neste ponto, acredito que os leitores devem esta se perguntando como que um endero virtual pode ser
mapeado em endereo fsico. A explicao mais detalhada ser apresentada na seo C.2.1. Por enquanto
basta acreditar que funciona.
Conforme visto antes, o processo acessa as pginas 7,8,5,9,2,7 na seqncia. Assim, aps trabalhar na
pgina virtual 7, a prxima pgina virtual a ser acessada a pgina virtual 8.
Quando o processo tentar acessar a pgina virtual 8, ocorrer outra armadilha, que far com que o
sistema operacional seja chamado. Digamos que desta vez, o SO determina que a pgina real 5 seja usada
para conter a pgina virtual 8. O mesmo ocorre com as outras pginas virtuais a medida que vo sendo
utilizadas. Ao final, a tabela de mapeamentos fica como indicado na tabela C.3.

importante observar que o processo todo controlado pelo Sistema Operacional sem necessidade de
interveno do usurio.
Uma pergunta natural aqui como que este mecanismo se comporta quando existem vrios processos
em execuo simultnea.
Para explicar este cenrio, considere que dois processos, processo A e processo B esto em execuo
simultnea. Cada processo tem a sua tabela de mapeamento de endereos virtuais para endereos fsicos.

APNDICE C. MEMRIA VIRTUAL

120
Pgina Virtual
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

Pgina Fsica
X
4
X
X
7
X
2
5
1
X
X
X
X
X
X

Tabela C.3: Tabela de mapeamento de endereos virtuais para endereos fsicos: Aps
acesso s pginas virtuais 7,8,5,9,2,8

Considere agora que as pginas virtuais do processo A esto mapeadas nas pginas fsicas 0, 1, 2, 3 e
4. O processo B tem pginas virtuais mepeadas nas pginas fsicas 5, 6, e 7. Observe que todas as pginas
fsicas esto ocupadas.
O processo B est em execuo e acessa uma pgina virtual que no foi mapeada em memria fsica.
Como j foi explicado, a tentativa de acessar a pgina virtual no mapeada em pgina fsica gera uma
armadilha. Esta armadilha desvia o fluxo para o sistema operacional, que incumbido da tarefa de mapear
aquela pgina virtual em alguma pgina fsica.
Como todas as pginas fsicas esto ocupadas, o sistema operacional ter de se desfazer de alguma
pgina fsica. O sistema operacional observa que a pgina fsica que est sem uso h mais tempo a pgina
3. Sendo assim, ele:
copia o contedo da pgina 3 em disco. Digamos, para o arquivo /swap/xxx;
vai na tabela de mapeamento do processo A e indica que aquela pgina virtual (digamos pgina
virtual 12) no est mais mapeada na pgina fsica 3, mas sim copiada em disco.
copia a pgina virtual de B para a pgina fsica 3.
passa o controle para o processo B.
O processo B continua sua execuo normal. Porm, em algum momento, o sistama operacional ir
retir-lo da execuo, e eventualmente, o processo A ganhar controle da CPU. Quando o fizer, possvel
que tente acessar a pgina virtual 12, que estava mapeada na pgina fsica 3. Porm, ao tentar acessar aquela
pgina, ocasionar um page fault.
A armadilha chamar o sistema operacional, que por sua vez ter de encontrar uma pgina fsica para
abrigar a pgina virtual 12 de A. Ao encontrar uma pgina candidata, digamos pgina real 7, o sistema
operacional ir copiar o contedo de /swap/xxx para a pgina fsica 7.
A pgina fsica 7 exatamente igual ao que era a pgina fsica 3 antes de ser salva em disco, e o processo
A nem sequer percebe a diferena.
O termo rea de swap usado para designar o local do disco onde as pginas removidas da memria
fsica so armazenadas.

C.2. PAGINAO

121

Figura C.3: MMU convertendo endereo virtual (7120)16 para endereo fsico
(2120)16 .

C.2.1 MMU
Em um sistema operacional com paginao, um processo comum jamais sabe qual o endereos da memria
fsica est utilizando. O nico que tem este privilgio o sistema operacional. Um processo comum tem
somente acesso a endereos da memria virtual. A traduo de endereos de memria virtual para memria
fsica feita pela MMU. Este dispositivo est embutido na CPU, e a funo dele receber um conjunto de
bits contendo um endereo virtual e fazer converso para o endereo fsico. O maneira com que a MMU faz
isso o objeto desta seo.
Como exemplo, considere novamente o exemplo em que o processo acessa as pginas virtuais 7, 8, 5,
9, 2, 7.
Considere que o rtulo _start est no endereo (7120)16 . Este endereo est na pgina virtual 7. todos
os endereos enre (28672)10 = (7000)16 e (32767)10 = (799916 esto na pgina virtual 7.
Quando o programa iniciado, o fluxo de execuo desvia para o rtulo _start. Neste momento, o
registrador program counter (PC) conter o valor (7120)16 . Pelo exemplo, sabemos que esta pgina virtual
foi mapeada para a pgina fsica 2. A MMU ir converver o endereo virtual (7120)16 em endereo fsico
(2120)16 somente quando o endereo for colocado no barramento que conecta a CPU e a memria.
A figura C.3 exemplifica esta converso. Como a figura mostra, o endereo de entrada (7120)16 .
Observe que no exemplo, o endereo de entrada contm desesseis bits, enquanto que o endereo de sada
contm 15 bits.

O que a MMU faz basicamente:


1. converter os quatro primeiros bits (nmero da pgina virtual) para trs bits (nmero da pgina fsica);
2. copiar os 12 bits restantes, sem alter-los.
Para fazer a primeira parte, a MMU utiliza uma tabela, chamada tabela de pginas. Esta tabela de
pginas a implementaao da tabela mapeamento apresentada nas tabelas C.1, C.2 e C.3. Para comprovar,
compare a figura C.3 e a tabela C.1.

APNDICE C. MEMRIA VIRTUAL

122

Vamos agora analisar um pouco melhor alguns aspectos do funcionamento da tabela de pginas apresentada na figura C.3.
O modelo de paginao leva em considerao que os endereos podem ser divididos em duas partes:
um parte indica o nmero da pgina (4 bits, 24 = 16 pginas), e o deslocamento dentro da pgina (12 bits,
212 = 4094 = 4Kbytes).
Vamos explorar um pouco mais este aspecto. A tabela C.4 corresponde enumerao de todos os
endereos virtuais possveis usando 16 bits.

Endereo
(0000000000000000)2
(0000000000000001)2
(0000000000000010)2

(0000111111111111)2
(0001000000000000)2
(0001000000000001)2
(0001000000000010)2

(1111111111111111)2

Nmero da Pgina
Virtual
0
0
0

Deslocamento
0
1
2

0
1
1
1

4093
0
1
2

15

4093

Tabela C.4: Relao entre endereos e as pginas


Os endereos esto em seqncia. Assim, a primeira linha pode ser lida usando-se o nmero binrio ou o par [nmero da pgina virtual, deslocamento]. Como exemplo, veja que o endereo virtual
(0001000000000000)2 indica a pgina 1, deslocamento 0.
Considere agora uma seqncia de endereos em uma mesma pgina virtual. Digamos (7120)16 ,
(722F )16 , (7450)16 . Todos estes endereos esto na pgina virtual 7.
O nosso exemplo diz que esta pgina virtual foi mapeada para a pgina fsica 2, ou seja, h uma cpia
do contedo da pgina virtual 7 na pgina real 2. Pela MMU, sabemos que os endereos virtuais sero
mapeados para os seguintes endereos: (2120)16 , (222F )16 , (2450)16 .
Lembrando que o primeiro byte da pgina virtual 7 est copiado no primeiro byte da pgina virtual 2,
que o segundo byte da pgina virtual est copiado no segundo byte da pgina virtual, podemos ver que o
contedo do endereo (120)16 da pgina virtual 7 est copiado no endereo (120)16 da pgina real 2.
Assim, o que a MMU faz basicamente dividir o endereo em pgina + deslocamento, e converter o
nmero da pgina virtual em nmero de pgina real.
Como a MMU converte todos os endereos que esto na CPU (endereos virtuais) para endereos fsicos na memria (e muito rpido, como pode ser observado), temos um situao onde ao olhar o valor
dos registradores da CPU vemos endereos virtuais, e um pouco antes de estes valores serem colocados no
barramento da memria, eles so convertidos para endereos fsicos como mostrado na figura C.4.

C.2. PAGINAO

123

Figura C.4: Posio da MMU

124

APNDICE C. MEMRIA VIRTUAL

Bibliografia
[aAaG05] Jonathan Corbet Alessandro Rubini Greg Kroah-Hartman. Linux Device Drivers, 3rd Edition.
OReilly Media, 2005.
[aPAW93] Peter Norton Peter Aitken and Richard Wilton. The Peter Norton PC Programmers Bible. Microsoft Press, 1993.
[Che]

Benjamin Chelf. Building and using shared libraries.

[Com95]

Tool Interface Standards Committee. Executable and Linking Format (ELF). Maio 1995. Pode
ser encontrado a partir de http://refspecs.freestandards.org/.

[Int04a]

Intel, editor. IA-32 Intel Architecture Software Developers Manual Volume 1: Basic Architecture. Intel, 2004. Order number 253665.

[Int04b]

Intel, editor. IA-32 Intel Architecture Software Developers Manual Volume 2A: Instruction Set
Reference, A-M. Intel, 2004. Order number 253666.

[Int04c]

Intel, editor. IA-32 Intel Architecture Software Developers Manual Volume 2B: Instruction Set
Reference, N-Z. Intel, 2004. Order number 253667.

[JDD+ 04] Jack Koziol, David Litchfield, Dave Aitel, Chris Anley, Sinan Eren, Neel Mehta, and Riley
Hassell. The Shellcoders Handbook: Discovering and Exploiting Security Holes. John Wiley
& Sons, 2004.
[Kow83]

Tomasz Kowaltowski. Implementao de Linguagens de Programao. Guanabara, 1983.

[Lev00]

John R. Levine. Linkers and Loaders. Morgan Kaufmann, 2000. Verso gratuita na internet em
http://www.iecc.com/linker.

[Paz07]

Bjorn Martins Paz. Uma Viagem ao Centro dos Executveis. Trabalho de Graduao - Depto.
Informtica - UFPR, 2007.

[Ree10]

Windows 7 Device Driver. Addison-Wesley, 2010.

[Tan01]

Andrew Tannenbaum. Modern Operating Systems. Prentice Hall, 2001.

[Whea]

David A. Wheeler. Program library howto. http://tldp.org/HOWTO/Program-Library-HOWTO.

[Wheb]

David A. Wheeler.
System v application binary interface.
64.org/documentation/abi.pdf.

125

http://www.x86-

Você também pode gostar