Escolar Documentos
Profissional Documentos
Cultura Documentos
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
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
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
3 Executable
12
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
16
Figura 1.1: Programa em execuo na memria virtual - rea de texto e dados globais
(formato ELF)
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
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.
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 =
ecx
esi
fs
eip
=
=
=
=
0x00000000
0x00000000
0x00000000
0x08048059
edx = 0x00000000
edi = 0x00000000
gs = 0x00000000
eflags = 0x00000202
Flags: IF
08048059
ald>
BB0D000000
19
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.
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
21
<_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:
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].
23
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).
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
2
3
4
5
6
7
8
9
26
1
2
3
4
5
6
7
8
9
10
11
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
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.
28
.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
.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
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 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.
33
34
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.
35
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
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.
37
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
.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.
39
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).
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
.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.
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.
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
.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.
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:
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.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
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.
48
2. preencha os valores das variveis do programa em C nos campos relacionados com as variveis na
figura 2.4.
1
2
3
4
5
6
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.
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
49
50
> ./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.
51
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.
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.
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. 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.
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.
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.
57
Only Memory
boot record
4 Master
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
59
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.
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
#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
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
4.1. HEAP
65
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.
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
72
e551c705
00000700
008d0402
8d61fcc3
.L$.....q.U..Q..
................
................
..........Y].a..
342e312e
52656420
2900
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
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.
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.
1
2
3
4
5
6
75
76
1
2
3
4
5
#include <stdio.h>
void a (char* s)
{
printf("%s", s);
}
Algoritmo 37: Programa exemplo: arquivo a.c
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.
78
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.
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:
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:
............
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.
81
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.
(...)
82
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);
}
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".
83
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:
>
>
>
>
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
1
2
#include <stdlib.h>
#include <dlfcn.h>
3
4
5
6
7
8
9
10
11
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
18
19
20
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
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.
87
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;
88
Arquivo
O1
O2
O3
O4
Endereos
1000-2016
2018-2937
2938-2F4C
2F50-42DF
Tabela 6.2: Composio do arquivo executvel E
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>.
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:
<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
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.
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.
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
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
94
00000031
0000003a
00000040
00000c01 R_386_32
00000d02 R_386_PC32
00000e01 R_386_32
00000000
00000000
00000000
G3
P4
G4
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
3 Considerando que o primeiro byte do arquivo est no endereo 0x0, o segundo byte est no de endereo
0x1, e assim por diante.
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].
O4-semVars.o
96
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.
98
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.
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]
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.
Linkage Table.
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
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.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
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
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
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
106
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
{z
} |{z} |{z}
CO
Op1 Op2
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 .
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.
108
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.
1
2
3
4
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:
109
%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 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).
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
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
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
116
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
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.
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.
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
C.2. PAGINAO
123
124
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]
[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]
[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]
[Tan01]
[Whea]
[Wheb]
David A. Wheeler.
System v application binary interface.
64.org/documentation/abi.pdf.
125
http://www.x86-