Download (Ebook) An Introduction to Assembly Programming with RISC-V by Edson Borin ISBN 9786500158113, 6500158113 ebook All Chapters PDF
Download (Ebook) An Introduction to Assembly Programming with RISC-V by Edson Borin ISBN 9786500158113, 6500158113 ebook All Chapters PDF
com
https://ebooknice.com/product/an-introduction-to-assembly-
programming-with-risc-v-47667784
OR CLICK HERE
DOWLOAD EBOOK
https://ebooknice.com/product/an-introduction-to-programming-
with-c-5291684
ebooknice.com
ebooknice.com
(Ebook) An Introduction to Network Programming with Java
by Jan Graba ISBN 9780321116147, 0321116143
https://ebooknice.com/product/an-introduction-to-network-programming-
with-java-1267336
ebooknice.com
ebooknice.com
https://ebooknice.com/product/an-introduction-to-programming-
with-c-6th-edition-2427428
ebooknice.com
ebooknice.com
book-cover.s •
55
addi
call
a0,a0,%lo( .LCD
printf
Unicamp
lui a0,%hi(.LC2)
57 addi a0,a0,%lo(.LC2)
58 call printf
59 lui a0,Mii(.LC3)
60 addi a0,a0,%lo(.LC3)
61 call printf
62 Iw ra,12(sp)
63 li a0,0
65
addi sp,sp,16
ra
1st edition
jr
An Introduction to Assembly Programming
with RISC-V
2
Copyright © 2021 Edson Borin
All rights reserved. This book or any portion thereof may not be reproduced
or used in any manner whatsoever without the express written permission of
the author except for the use of brief quotation in a book review.
ISBN:978-65-00-15811-3
First edition 2021
Edson Borin
Institute of Computing - University of Campinas
Av. Albert Einstein, 1251
Cidade Universitaria Zeferino Vaz
Barao Geraldo - Campinas - SP - Brasil
www.ic.unicamp.br/~edson
13083-852
An updated version of this book and other material may be available at:
www.riscv-programming.org
3
Foreword
This book focuses on teaching the art of programming in assembly language, using
the RISC-V ISA as the guiding example. Towards this goal, the text spans, at an
introductory level, the organization of computing systems, describes the mechanics of
how programs are created and introduces basic programming concepts including both
user level and system programming. The ability to read and write code in low-level
assembly language is a powerful skill to be able to create high performance programs,
and to access features of the machine that are not easily accessible from high-level
languages such as C, Java or Python, for example to control peripheral devices.
The book introduces the organization of computing systems, and the mechan
ics of creating programs and converting them to machine-readable format suitable
for execution. It also teaches the components of a program, or how a programmer
communicates her intent to the system via directives, data allocation primitives and
finally the ISA instructions, and their use. Basic programming concepts of control
flow, loops as well as the runtime stack are introduced.
Next the book describes the organization of code sequences into routines and
subroutines, to compose a program. The text also addresses issues related to system
programming, including notions of peripheral control and interrupts.
This text, and ancillary teaching materials, has been used in introductory classes
at the University of Campinas, Brazil (UNICAMP) and has undergone refinement
and improvement for several editions.
Mauricio Breternitz
Principal Investigator & Invited Associate Professor
ISTAR ISCTE Laboratory
ISCTE Institute Universitario de Lisboa
Lisbon, Portugal
4
Notices:
• Please, report typos and other issues to Prof. Edson Borin ([email protected].
br).
5
Contents
Foreword 4
Glossary 11
Acronyms 14
6
4 Assembly language 37
4.1 Comments................................................................................................................. 39
4.2 Assembly instructions......................................................................................... 39
4.3 Immediate values................................................................................................... 40
4.4 Symbol names ....................................................................................................... 40
4.5 Labels........................................................................................................................ 41
4.6 The location counter and theassembling process........................................ 42
4.7 Assembly directives................................................................................................ 44
4.7.1 Adding values to the program.............................................................. 44
4.7.2 The .section directive........................................................................ 46
4.7.3 Allocating variables on the.bss section........................................... 47
4.7.4 The .set and .equ directives.............................................................. 48
4.7.5 The .globl directive............................................................................... 49
4.7.6 The .align directive............................................................................... 49
II User-level programming 51
5 Introduction 52
7
7.4.1 Searching for the maximum value on an array............................... 85
8 Implementing routines 87
8.1 The program memory layout ........................................................................... 87
8.2 The program stack................................................................................................ 87
8.2.1 Types of stacks......................................................................................... 90
8.3 The ABI and software composition................................................................. 91
8.4 Passing parameters to and returning values from routines.................... 91
8.4.1 Passing parameters to routines........................................................... 91
8.4.2 Returning values from routines........................................................... 93
8.5 Value and reference parameters........................................................................ 93
8.6 Global vs local variables..................................................................................... 95
8.6.1 Allocating local variables on memory................................................. 96
8.7 Register usage policies......................................................................................... 98
8.7.1 Caller-saved vs Callee-saved registers................................................. 99
8.7.2 Saving and restoring the return address.............................................. 100
8.8 Stack Frames and the Frame Pointer................................................................. 100
8.8.1 Stack Frames................................................................................................. 100
8.8.2 The Frame Pointer....................................................................................... 101
8.8.3 Keeping the stack pointer aligned........................................................... 102
8.9 Implementing RISC-V ilp32 compatible routines..........................................102
8.10 Examples................................................................................................................... 103
8.10.1 Recursive routines...................................................................................... 103
8.10.2 The standard “C” library syscall routines..........................................104
8
A RV32IM ISA reference card 134
9
10
Glossary
binary digit is a digit that may assume one of two values: “0” (zero) or “1” (one).
10, 11, 14
byte addressable memory is a memory in which each memory word stores a single
byte. 3-5, 10, 18-21, 23, 54
Control and Status Register , or CSR, is an internal CPU register that exposes
the CPU status to the software and allow software to control the CPU. 10, 120,
121, 129, 131
endianness refers to the order in which the bytes are stored on a computing system.
There are two common formats: little-endian and big-endian. The little-endian
format places the least significant byte on the memory position associated with
the lowest address while the big-endian format places the most significant byte
on the memory position associated with the highest address. 10, 19, 46, 64-67
Instruction Set Architecture defines the computer instructions set, including, but
not limited to, the behavior of the instructions, their encoding, and resources
that may be accessed by the instructions, such as CPU registers. 4, 10, 49, 53,
54, 56, 60, 62, 64-67, 70-72, 74, 75, 84, 120, 127, 128
11
integer overflow occurs when the result of an arithmetic operation on two integer
m-bit binary numbers is outside of the range that can be represented by an
m-bit binary number. 10, 15, 16
ISA native datatype is a datatype that can be naturally processed by the ISA. 10,
54, 64
load instruction is an instruction that loads a value from main memory into a
register. 10, 56
main memory is a storage device used to store the instructions and data of pro
grams that are being executed. 2-5, 10, 12, 18, 20, 21, 23, 27, 32, 33, 35, 36,
48, 107-112, 114-119, 123-125, 128
native program is a program encoded using instructions that can be directly exe
cuted by the CPU, without help from an emulator or a virtual machine. 2, 10,
24, 26
opcode the opcode, or operation code, is a code (usually encoded as a binary num
ber) that indicates the operation that an instruction must perform. 10, 57
peripherals are input/output, or I/O, devices that are connected to the computer.
Examples of peripheral devices include video cards (also known as graphics
cards), USB controllers, network cards, etc.. 2, 10, 107
persistent storage is a storage device capable of preserving its contents when the
power is shut down. Hard disk drives (HDDs), solid state drives (SSDs), and
flash drives are example of persistent storage devices. 2, 3, 10, 107
privilege level defines which ISA resources are accessible by the software being ex
ecuted. 10, 120, 127, 128
privilege mode defines the privilege level for the currently executing software. 10,
13, 128-131
program counter or PC, is the register that holds the address of the next instruc
tion to be executed. In other words, it holds the address of the memory position
that contains the next instruction to be executed. It is also known as instruction
pointer, or IP, in some computer architectures. 10, 55
12
pseudo-instruction is an assembly instruction that does not have a corresponding
machine instruction on the ISA, but can be translated automatically by the
assembler into one, or more, alternative machine instructions to achieve the
same effect. 10, 39, 40, 56, 58, 68, 69
register is a small memory device usually located inside the Central Processing Unit
(CPU) for quick read and write access. 3, 10
row-major order specifies that the elements of a two-dimensional array are orga
nized in memory row by row. In this context, the elements of the first row are
placed first then the elements of the second row are placed after the elements
of the first one and so on. 10, 21
stack pointer is a pointer that points to the top of the program stack. In other
words, it holds the address of the top of the program stack. In RISC-V, the
stack pointer is stored by the sp register.. 10
unprivileged ISA is the sub-set of the ISA that is accessible by the software running
on unprivileged mode. 10, 55, 128
unprivileged mode is the privilege mode with least privileges. In RISC-V, it is the
User/Application privilege mode. 10, 13, 128
13
Acronyms
ABI Application Binary Interface. 10, 54, 84, 91-94, 99, 102, 103, 125
bit Binary digit. 2-5, 10-20, 22, 23, 25, 28, 29, 35, 37, 40, 44-46, 48-50, 102, 108-114,
120, 121, 125
CPU Central Processing Unit. 2-5, 10-13, 32, 36, 49, 52, 107-112, 114-125, 128-132
ISA Instruction Set Architecture. 4, 10, 13, 24-26, 38-40, 49, 50, 55, 109-111, 120,
124, 125, 128, 129
UTF-8 Universal Coded Character Set (or Unicode) Transformation Format - 8-bit.
10, 16-18
14
Part I
Introduction to computer
systems and assembly
language
1
Chapter 1
Execution of programs: a
10,000 ft overview
There are several ways of encoding a computer program. Some programs, for ex
ample, are encoded using abstract instruction sets and are executed by emulators or
virtual machines, which are other programs designed to interpret and execute the
abstract instruction set. Bash scripts, Java byte-code programs, and Python scripts
are common examples of programs that are encoded using abstract instruction sets
and require an emulator or a virtual machine to support their execution.
A native program is a program encoded using instructions that can
be directly executed by the computer hardware, without help from an
emulator or a virtual machine. In this book, we focus our discussion on native
programs. Hence, from now on, whenever we use the term “program”, unless stated
otherwise, we are referring to native programs.
Native program instructions usually perform simple operations, such as adding or
comparing two numbers, nonetheless, by executing multiple instructions, a computer
is capable of solving complex problems.
Most modern computers are built using digital electronic circuitry. These machines
usually represent information using voltage levels that are mapped to two states,
HIGH and LOW, or “1” (one) and “0” (zero). Hence, the basic unit of information
on modern computers is a binary digit, i.e., “1” or “0”. Consequently, information
and instructions are encoded as sequences of binary digits, or bits.
• Main memory: The main memory is used to store the instructions and data of
programs that are being executed. The main memory is usually volatile, hence,
if the computer is turned off, its contents are lost.
• Central Processing Unit: the Central Processing Unit, or CPU, is the com
ponent responsible for executing the computer programs. The CPU retrieves
programs’ instructions from the main memory for execution. Also, when execut
ing instructions, the CPU often reads/writes data from/to the main memory.
2
1.1. MAIN COMPONENTS OF COMPUTERS
Figure 1.1 illustrates a computer system in which the CPU, the main memory, a
persistent storage device (HDD) and two I/O devices are connected through a system
bus.
Addresses Memory
locations
(a)
Figure 1.2: Organization of a byte addressable memory with its contents represented
in the binary (a) and the hexadecimal (b) bases.
• Registers: a CPU register is a small memory device located inside the CPU.
The CPU usually contains a small set of registers. RISC-V processors, for ex
ample, contain thirty-one 32-bit registers1 that can be used by programs to
store information inside the CPU. Computers often contain instructions that*
1A 32-bit register is a register that is capable of storing 32 bits, i.e., values composed of 32 bits.
copy values from the main memory into CPU registers, known as “load” in
structions, and instructions that copy values from the CPU registers into the
main memory, known as “store” instructions.
• A control unit: the control unit is the unit responsible for orchestrating the
computer operation. It is capable of controlling the datapath and other compo
nents, such as the main memory, by sending commands through the bus. For
example, it may send a sequence of commands to the datapath and to the main
memory to orchestrate the execution of a program instruction.
Accessing data on registers is much faster than accessing data on the main memory.
Hence, programs tend to copy data from memory and keep them on CPU registers to
enable faster processing. Once the data is no longer needed, it may be discarded or
saved back on the main memory to free CPU registers.
The Instruction Set Architecture, or ISA, defines the computer instructions set,
including, but not limited to, the behavior of the instructions, their encoding, and
resources that may be accessed by the instructions, such as CPU registers. A program
that was generated for a given ISA can be executed by any computer that implements
a compatible ISA.
ISAs tend to evolve over time, however, ISA designers try to keep newer ISA
versions compatible with previous ones so that legacy code, i.e., code generated for
previous versions of the ISA, can still be executed by newer CPUs. For example, a
program that was generated for the 80386 ISA can be executed by any processor that
implements this or any other compatible ISAs, such as the 80486 ISA.
Memory
Address
words
■ ■■ ...
000100112 8000
000001012 8001
000101012 8002
00000000., 8003
Assembly language (RV32I)
loop: / 100100112 8004
addi a0, a0, 1 100001012 8005
addi al, al, 1
beq a0, al, loop •. 111101012 8006
11111111 2 8007
111000112 8008
000011002 8009
101101012 8 0 0A
111111102 800B
■ ■■ ...
Figure 1.3: Three RV32I instructions stored on a byte addressable memory starting
at address 8000.
performed by a simple RV32I CPU. First, the CPU uses the address in the PC to fetch
an instruction (a sequence of four memory words, i.e., 32 bits) from main memory
and store it on an internal register called IR. Then, it updates the PC so it points to
the next instruction in memory. Finally, it executes the instruction that was fetched
from memory. Notice that when executing the instruction, the CPU may also access
the main memory to retrieve or store data.
Algorithm 1: RV32I instructions execution cycle.
1 while True do
2 // Fetch instruction and update PC ;
3 IR ^ MainMemory[PC] ;
4 PC ^ PC+4;
5 ExecuteInstruction(lR);
6 end
To execute a program, the operating system essentially loads the program into the
main memory (e.g., from a persistent storage device) and sets the PC so it points to
the program entry point.
Data representation on
modern computers
This chapter discusses how information is represented on computers. First, Section 2.1
introduces the concepts of numeral systems and the positional notation. Then, sec
tions 2.2 and 2.3 discuss how numbers and text are represented on computers, respec
tively. Next, Section 2.4 shows how data is organized in memory. Finally, Section 2.5
discusses how instructions are encoded.
• D1o be the set of symbols used in the decimal numeral system, i.e., D1o =
{“0”, “1”, “2”, “3”, “4”, “5”, “6”, “7”, “8”, “9”}; and
For example, the number one thousand nine hundred and sixty nine is represented
by the sequence 1969. In this case, m = 4, d3 = “1”, d2 = “9”, d1 = “6”, and
d0 = “9”
Let:
6
2.1. NUMERAL SYSTEMS AND THE POSITIONAL NOTATION
The value of a number with m digits in the decimal numeral system is computed
by Equation 2.2.
i<m
Notice that the value of each digit di, i.e., the contribution of each digit di to the
number value, depends on the symbol used and its position (index i) on the sequence.
For example, the number 1969 has four digits: d3 = 1,d2 = 9, d1 = 6, d0 = 9. The
value of digit d2 is nine hundred while the value of digit d0 is nine.
A positional numeral system is a numeral system in which the value of
a digit di depends on the value of the symbol used on the digit and also
on the position of the digit on the sequence of digits. The decimal numeral
system is a positional numeral system. The unary and the Roman numeral systems,
on the other hand, are common examples of non-positional numeral systems.
The base, or radix, of a numeral system is the number of different
symbols a digit may assume to represent the numbers. The base of the
“unary numeral system” is one while the base of the “decimal system” is ten.
The decimal, binary, octal and hexadecimal numeral systems are posi
tional numeral systems that are frequently used when programming com
puters. The only difference between these numeral systems is the base. While the
base of the decimal numeral system is ten, the base of the binary, the octal, and the
hexadecimal numeral systems are two, eight, and sixteen, respectively.
Let:
• base be the base, or radix, of the positional numeral system;
• Dbase be the set of symbols used in the positional numeral system (e.g., D2 =
{“0”, “1”}); and
• dbase be the ith digit on a number represented in the positional numeral system,
i.e., dbase G Dbase;
number =d
numberbase — dm 1 dm 2 • • • d
base dbase
1 d^
dbase °
dbase (2 3))
(2.3
Let:
The value of a natural number with m digits in any of these positional numeral
systems is defined by Equation 2.4.
i<m
For example, the value of any natural number represented on the binary numeral
system (base — 2) is defined by Equation 2.5
i<m
Notice that the value of the sequence 11 is three on the binary numeral system
(1 x 21 +1 x 20) while it is eleven on the decimal numeral system (1 x 101 + 1 x 10 0)
and seventeen on the hexadecimal numeral system (1 x 161 + 1 x 160).
When working with multiple numeral systems it is often necessary to annotate the
numbers so that it is possible to identify the numeral system being used, and hence, its
value. A common notation is to append a subscribed suffix to the number indicating
the base of the positional numeral system. For example, the value of number 1110 is
eleven while the value of number 112 is three1.
Appending a subscribed suffix to the number is not a natural way of annotating
numbers in computer programs. In these cases, a common approach is to append a
prefix that indicates the base. For example, in “C”, the programmer may use the
prefix “0b”/“0”/“0x” to indicate that the number is in the binary/octal/hexadecimal
base, i.e., base 2/8/16. In “C”, numbers that lack a prefix belong to the decimal
numeral system.
The binary and octal numeral systems use a subset of the symbols used on the
decimal numeral system to represent the numbers. The hexadecimal numeral system,
on the other hand, requires more than ten symbols, hence, new symbols are needed.
In this case, the first letters of the alphabet are used to complement the set of symbols.
Table 2.1 shows the symbols used in each one of these positional numeral systems and
their corresponding values.
Used in base
Symbol symbol-value
2 8 10 16
“0” zero X X X X
“1” one X X X X
“2” two X X X
“3” three X X X
“4” four X X X
“5” five X X X
“6” six X X X
“7” seven X X X
“8” eight X X
“9” nine X X
“A” ten X
“B” eleven X
“C” twelve X
“D” thirteen X
“E” fourteen X
“F ” fifteen X
Table 2.1: Set of symbols used in binary, octal, decimal, and hexadecimal numeral
systems and their respective values.
Table 2.2 shows how values zero to twenty are represented in the hexadecimal,
decimal, octal, and binary numeral systems.
Numeral system
Value
Hexadecimal Decimal Octal Binary
zero 016 010 08 02
one 116 110 18 12
two 216 210 28 102
three 316 310 38 112
four 416 410 48 100 2
five 516 510 58 101 2
six 616 610 68 110 2
seven 716 710 78 111 2
eight 816 810 108 10002
nine 916 910 118 10012
ten A16 1010 128 10102
eleven B16 1110 138 10112
twelve C16 1210 148 11002
thirteen D16 1310 158 11012
fourteen E16 1410 168 11102
fifteen F16 1510 178 11112
sixteen 1016 1610 208 100002
seventeen 1116 1710 218 100012
eighteen 1216 1810 228 100102
nineteen 1316 1910 238 100112
twenty 1416 2010 248 101002
Table 2.2: Values zero to twenty represented in the hexadecimal, decimal, octal, and
binary numeral systems.
one must find a sequence of digits d^se1 dma-e2 • • • d1ase 0ase so that: d
Notice that v(d0) and V0 are equivalent to the remainder and the quotient of the
division of V by b. Hence, to find out the symbol value of digit d0 (i.e., v(d0)) it
suffices to compute the remainder of the division of V by b.
Using the same reasoning, the symbol value of digit d1 may be computed by
dividing V0 by b. Notice that the remainder of the division of V0 by b is equal to
v(d 1).
Let symbol from, value(v, b) be a function that returns the symbol used to rep
resent value v on base b (e.g., symbol-from-value(eleven, 16) = “B”), Algorithm 2
shows an algorithm to compute the sequence of digits dml-1dm1-2 • • • d1d0 that repre
To illustrate the use of Algorithm 2 lets compute the sequence of digits dm1-1 d'm‘ —
2
• • • d1 d0 that represent value twenty six on base 2. First, we divide twenty six by
two. The remainder of this division is zero and the quotient is thirteen, hence, v(d2)
= zero and d0 = “0”. This process is illustrated in Figure 2.1 (a). To compute v(d 1)
we now divide thirteen (i.e., the quotient of the last division) by two. The remainder
of this division is one and the quotient is six, hence, v(d2) = one and d1 = “1”.
This partial result is illustrated in Figure 2.1 (b). To compute v(d2) we now divide
six (the previous quotient) by two. The remainder of this division is zero and the
quotient is three, hence, v(d2) = zero and d2 = “0”. Now, we divide three by two.
The remainder of this division is one and the quotient is one, hence, v(d3) = one
and d3 = “1”. Again, we divide the last quotient (one) by two. The remainder of
this division is one and the quotient is zero, hence, v(d4) = one and d42 = “1”. Since
the last quotient is zero, the process is complete. As a result, the sequence 110102
represents value twenty six on the binary numeral system. Figure 2.1 (c) shows all
the quotients and remainders computed during the execution of Algorithm 2.
26 2 26 2 26 2
- 26 13 - 26 13 2 - 26 13 , 2
0 v(d02) = 0 - 12 6 v(d 02) = 0 - 12 6 2
q quotient
v(d12) = 1 v(d12) = 1 -6 3 | 2
remainder
22) = 0
v(d* -2 1 2
v(d32) =1 -0 0
v(d 42) =1
26 i 16 26 16
v(d 016) = 10 => d 016= "A"
- 16 1 - 16 1 | 16
converting between these bases can be done by replacing subsets of consecutive four
bits by single hexadecimal digits, and vice versa. For example, Equation 2.8 illustrates
how number 3E816 can be converted to binary and Equation 2.9 shows how number
101101011101012 can be converted to hexadecimal.
To illustrate this concept, Table 2.3 shows the list of unsigned numbers that can
be represented with three-bit words.
Value in the
Three-bit word
unsigned representation
000 010
001 110
010 210
011 310
100 410
101 510
110 610
111 710
Table 2.3: Unsigned numbers that can be represented with three-bit words.
The “signal and magnitude” is a number representation that can be used to represent
signed numbers. In this representation, signed numbers are represented as a sequence
of m bits so that bit dm1-1 represents the signal of the number and the remaining
bits, i.e., bits dm-2 to d0, represent the magnitude. The magnitude value can be
computed using Equation 2.5. In this context, in case bit dm1-1 is “1”, then the
number is negative, otherwise, it is non-negative. Table 2.4 show the values of three-
bit words on the unsigned and signal and magnitude representations.
Value
Three-bit word signal and
unsigned
magnitude
000 010 010
001 110 110
010 210 210
011 310 310
100 410 —010
101 510 — 110
110 610 —210
111 710 -310
Table 2.4: Values of three-bit words on the unsigned and signal and magnitude rep
resentations.
The “signal and magnitude” representation can represent numbers in the range
[—(2m-1 — 1) .. (2m-1 — 1)] with a sequence of m bits.
One’s complement
Value
Three-bit word signal and one’s two’s
unsigned
magnitude complement complement
000 010 010 010 010
001 110 110 110 110
010 210 210 210 210
011 310 310 310 310
100 410 —010 —310 —410
101 510 — 110 —210 —310
110 610 —210 — 110 —210
111 710 —310 —010 — 110
Table 2.5: Values of three-bit words on the unsigned, signal and magnitude, one’s
complement and two’s complement representations.
The “one’s complement” representation can represent numbers in the range [—(2m-1—
1) .. (2m-1 — 1)] with a sequence of m bits.
Two’s complement
Figure 2.3: The addition of two numbers produces the same sequence of bits both in
the unsigned numbers representation and the two’s complement representation.
The two’s complement representation can represent numbers in the range [— (2m-1) .. (2m-1-
1)] with a sequence of m bits.
3This is only true if the output is represented using the same number of bits as the inputs (i.e.,
m bits) and if any overflow beyond these bits are discarded. This is usually the case in modern
computers.
4As discussed in previous section, adding (subtracting) two m-bit unsigned binary numbers pro
duces the same sequence of m-bits as adding (subtracting) two m-bit signed numbers on the two’s
complement representation. Hence, the same approach used for unsigned binary numbers may be
used to perform addition and subtraction operations on signed numbers using the two’s complement
representation.
Figure 2.4: Adding two three-bit binary numbers. (a) Red arrows indicate where the
carry out comes from. (b) Simplified representation (without arrows).
In these cases, “some value” is borrowed from the left digit and this borrowed value
must be accounted for when performing the subtraction on the left digit.
Figure 2.5 illustrates the subtraction of two three-bit unsigned binary numbers.
Figure 2.5 (b) shows the first step, in which the least significant digits are subtracted.
Since “1” cannot be subtracted from “0”, some value must be borrowed from the left
column. The “*” character indicates that value had to be borrowed from the left
column. The result in this column is “1”, since “10” (two) minus “1” (one) is “1”.
Figure 2.5 (c) illustrates the operation on the second least significant digit. Since
the right column borrowed from this column, the first operand is now “0”. Again,
since “1” cannot be subtracted from “0”, some value must be borrowed from the
left column. The result in this column is “1”, since “10” (two) minus “1” (one) is
“1”. Figure 2.5 (d) shows the subtraction of the most significant digits (zero minus
zero) and the final result. Figure 2.5 (e) shows a simplified representation of the
subtraction.
* * *
* * * *
10 10 10 (610) 1 1 0 (610)
— 011 (310) — 011 (У
0 1 1 (Зю) 0 1 1 (Зю)
(d) (e)
Figure 2.5: Subtraction of two three-bit binary numbers. (a) The digits of both num
bers are aligned on columns. (b) First, the least significant digits are subtracted - the
“*” character indicates that some value was borrowed from the left column. (c) The
second least significant digits are subtracted - again, the “*” character indicates that
some value was borrowed from the left column. (d) Finally, the most significant digits
are subtracted producing digit “0”. (e) Simplified representation of the subtraction.
unsigned binary numbers causes an integer overflow. In this case, adding one to seven
should result in eight, however, the value eight cannot be represented using only three
bits on the unsigned binary representation.
Even though the operation illustrated on Figure 2.6 characterizes an integer over
flow on the unsigned binary representation, it does not characterize an integer overflow
on the signed (two’s complement) binary representation. In this case, the operation
is adding one (001) to minus one5 (111) and the expected result, i.e., zero, can be
represented by a three-bit unsigned binary number.
Figure 2.7 shows an example in which the addition of two three-bit signed binary
numbers using the two’s complement method causes an integer overflow. In this case,
however, there was not integer overflow on the unsigned binary number representation.
Notice that the result of the operation is as expected, i.e., four (100), on the unsigned
binary representation.
11 ^ carry digits
0 1 1 (310)
+ 001 (110)
1 0 0 (-410)
Figure 2.7: Example of an integer overflow on the signed binary representation. The
result of three plus one cannot be represented by a three-bit signed binary number
using the two’s complement representation.
5Notice that the three-bit sequence 111 represents the value minus one on the two’s complement
representation.
6Control characters are not intended to represent printable information. A line feed, carrier return
and backspace are examples of control characters on computers.
7https://googleblog.blogspot.com/2008/05/moving-to-unicode —51.html
Table 2.6: Subset of the characters encoded by the ASCII character encoding stan
dard. Hex. and Dec. columns show the encoding value in hexadecimal and decimal
representation while the Char. column shows the symbol encoded by the character.
W3Techs web site8 indicated that more than 95.5 % of the world wide web websites
are encoded with the UTF-8 character encoding standard.
The “Unicode (or Universal Coded Character Set) Transformation Format - 8-
bit”, or UTF-8 for short, is a variable-width character encoding standard. In this
standard, each character may be represented by one, two, three, or four bytes, i.e.,
one, two, three, or four 8-bit numbers. Common characters, such as letters “a”, “b”,
and “c”, are represented by a single byte, while more exotic ones are represented
using multiple bytes. The euro currency sign (€), for example, is encoded using three
bytes: 111000102, 100000102, and 101011002.
The UTF-8 standard was designed to be backward compatible with the ASCII
standard. Hence, ASCII characters are represented on the UTF-8 standard using a
single byte with the the same value. For example, letter “a” is represented by value
ninety seven in both standards. In this way, a software designed to work with the
UTF-8 standard can naturally open and handle ASCII encoded files.
Texts are represented in computers as sequences of characters on mem
ory. For example, the word “Yes” is represented by a sequence of three characters
(“Y”, “e”, and “s”) stored on consecutive memory positions. In case the ASCII
character encoding standard is being used, the three consecutive memory positions
will contain values 12110, 10110, and 11510, respectively. Figure 2.8 illustrates how
the word “maca”9 is represented in three different character encoding standards: the
UTF-8, the ISO-LATIN-1 and the Mac OS Roman. Each square represents a byte
and the values inside the squares are in hexadecimal. Notice that the UTF-8 standard
requires two bytes to represent letter “c” and two bytes to represent letter “a”.
M a 9 a M a 9 a M a a
4D 61 E7 E3 4D 61 8D 8B 4D 61 C3 A7 C3 A3
ISO-LATIN-1 Mac OS Roman UTF-8
Figure 2.8: Word “maca” represented in three different character encoding standards:
the UTF-8, the ISO-LATIN-1 and the Mac OS Roman.
8https://w3techs.com/technologies/overview/character_encoding
9 “Maca” is the word for apple in Portuguese.
1 #include<stdio.h>
2 char name1[] = "John";
3 char name2[] = {0x4a, 0x6f, 0x68, 0x6e, 0x00};
4 int main()
5 {
6 printf("Name 1: \"%s\"\n", namel);
7 printf("Name 2: \"%s\"\n", name2);
8 printf("Size of name 1 = %d\n", sizeof(name1));
9 printf("Size of name 2 = %d\n", sizeof(name2));
10 return 0;
11 }
Both strings (name1 and name2) in previous code require five memory words to
be stored on memory11. In fact, since the hexadecimal values used in line 3 are
the values for the “J”, “o”, “h”, and “n” letters on the ASCII and UTF-8 encoding
standards, both string are identical. The following listing shows the output of the
previous program.
1 Name 1: "John"
2 Name 2: "John"
3 Size of name 1=5
4 Size of name 2=5
Figure 2.9: 32-bit number 00000000 00000000 00000100 000000012 (102510) stored on
four consecutive memory words using the (a) little-endian and the (b) big-endian
endianness formats.
Vector elements are usually organized in a linear fashion on the memory. Hence,
when translating the previous code into machine language, all elements (int values) of
vector V are placed in consecutive memory positions - starting with the first element,
i.e., V[0]. The base address of an array is the address of the first memory
word that is being used to store the array elements. Assuming the base address
of vector V is 000, then the first element (V[0]) is stored starting at memory address
000. Also, assuming each element requires four memory words13, the second element
12This is a property of the “C” programming language. Other languages, such as Pascal, associate
the first element with index one.
13The “C” int type is used to represent integer numbers and is usually mapped to 32-bit signed
numbers.
is stored starting at memory address 004 and the third one starting at memory address
008. Figure 2.10 illustrates the contents of vector V placed on memory starting at
address 000. Notice that, in this example, each element is a 32-bit number that is
stored on four consecutive memory words using the little-endian format.
Address Contents
000 000010012
001 000000002
■v[0] = 910
002 000000002
003 000000002
004 000010002
005 000000002
• v[1] = 810
006 000000002
007 000000002
008 000000012
009 000000002
■v[2] = 110
010 000000002
011 000000002
The previous example showed an array of int elements. Nonetheless, in “C”, the
programmer may also create arrays of other types. For example, one may create an
array of char, in which each element occupies only one byte, an array of double, in
which each element occupies 8 bytes, or even an array of a new type defined with the
struct operator, in which each element may occupy several bytes.
Let:
In “C”, and several other programming languages, each element V[i] occupies
elem size memory words of a byte addressable memory and is placed at the main
memory starting at address &V[i], which is defined by Equation 2.12.
The way two-dimensional arrays are organized on memory depends on the pro
gramming language. In “C”, elements are grouped by row and each row is placed on
memory consecutively. Hence, in the previous example, the elements of the first row,
i.e., M[0][0]=7, M[0][1]=9, and M[0][2]=11, are placed first on memory. Then, the
elements of the second row, i.e., M[1][0]=2, M[1][1]=8, and M[1][2]=1, are placed
after the elements of the first row. This way of organizing two-dimensional arrays on
memory is known as row-major order.
Let:
In “C”, each element A [x][y] occupies elemsize memory words of a byte addressable
memory and is placed at the main memory starting at address &A[x][y], which can
be computed using Equation 2.13.
Notice that Equation 2.13 adds to the base address (Aaddr) two offsets: offset 1
and offset 2. The first offset is the amount of space in bytes required to store all
elements that belong to previous rows, i.e., rows that must be placed before row x.
The second offset is the amount of space in bytes required to store all elements that
belong to the same row but must be placed before element A[x][y], i.e., the elements
that has a column index less than y.
i struct user_id {
2 int id;
з char name[256];
4 short level;
5 };
6
9 void print_manager()
10 {
ii printf("Manager id = %d\n", manager.id);
12 printf("Manager name = %s\n", manager.name);
13 printf("Manager level = %d\n", manager.level);
14 }
Address Contents
000 000000012
001 000000002
■ id
002 000000002
003 000000002
004 010010102 n name[0]
... ...
259 000000002 * name[255]
260 000000002
• level
261 000001112
Figure 2.11 : Elements of variable manager stored on memory starting at address 000.
Figure 2.12 : Encoding of two different instructions: (a) instruction mov, which belongs
to the x86 instruction set architecture, and (b) instruction add, which belongs to the
RISC-V instruction set architecture.
parameters. These parameters specify that the add operation must be performed
using the values stored in registers x1 and x0 and the result stored in register x3,
which are identified by values 00012 , 00 002, and 00112 on fields rs1, rs2, and rd,
respectively.
Most modern computers store the code, i.e., the program instructions, on the same
memory they store the data - the main memory. Also, modern computer instructions
are encoded using multiples of 8 bits so that they fit the size of multiple memory words
on a byte addressable memory. Figure 2.13 shows an example of how a program
written in x86 assembly language is mapped to machine language and stored on a
byte addressable memory. Notice that, the first instruction, push $ebp, is encoded
using one byte while the third one, imul $113, 12(%ebp), %eax, is encoded using
four bytes. Also, notice that instructions are placed sequentially on memory, in the
same order they appear on the original assembly program.
Figure 2.13 : Mapping x86 instructions from an assembly language program to mem
ory.
This chapter presents the main concepts and elements of assembly, object, and exe
cutable files.
i int main()
2 {
з int r = func (10)
4 return r+1;
5 }*
1https://gcc.gnu.org/
24
3.1. GENERATING NATIVE PROGRAMS
An assembly program is also a program encoded as a plain text file. The following
code shows an example of a program written using the RV32I assembly language.
This program has the same semantics as the previous C program.
i .text
2 .align 2
з main:
4 addi sp,sp,-16
5 li a0,10
6 sw ra,12(sp)
7 jal func
8 lw ra,12(sp)
9 addi a0,a0,1
10 addi sp,sp,16
11 ret
Different from high-level languages, assembly language is very close to the ISA.
For example, the previous assembly program contains references to instructions (e.g.,
addi, li, ...) and registers (e.g., sp, ra, a0) that belong to the RV32I ISA. Lines
4 to 11 of the previous code contain assembly instructions, which are converted by
the assembler into RV32I machine instructions. As a consequence, they are ISA
dependent, i.e., an assembly program generated for one ISA is usually not compatible
with other ISAs.
Machine language is a low-level language that can be directly processed
by a computer’s central processing unit (CPU). An assembler is a tool
that translates a program in assembly language into a program in machine
language. For example, it converts assembly instructions (encoded as sequences of
ASCII characters) into machine instructions (encoded as sequences of bits accordingly
to the ISA). Each assembly language is associated with a given ISA.
The “GNU Assembler”2 tool, or as, is an assembler that is capable of translat
ing programs written in several assembly languages into machine language for their
respective ISAs. In this book we will use the as tool to translate RV32IM assembly
programs to machine language programs. The following command line illustrates how
the riscv64-unknown-elf-as tool, a version of the GNU Assembler that generates
code for RISC-V ISAs, can be invoked to assemble a RV32I assembly program. In
this example, the RV32I assembly program is stored on the main.s file and the result,
a file that contains code in machine language, will be stored on the main.o file.
Assemblers usually produce object files that are encoded in binary and contains
code in machine language. The object file also contains other information, such as
the list of symbols (e.g., global variables and functions) defined in the file. There
are several known file formats used to encode object files. The Executable and
Linking Format, or ELF, is frequently used on Linux-based systems while the
Portable Executable format, or PE, is used on Windows-based systems. The
riscv64-unknown-elf-as tool, used in the previous example, produces an ELF-based
object file.
Even though the object file produced by the assembler contains code in machine
language, it is usually incomplete in the sense that it may still need to be relocated
(more on relocation later) or linked with other object files to compose the whole
program. For example, the code in an object file may need to be linked with the C
library so that the program can invoke the printf function. As a consequence, the
object file produced by the assembler is not an executable file.
A linker is a tool that “links” together one or more object files and
produces an executable file. The executable file is similar to an object file in the
2https://www.gnu.org/software/binutils/
sense that it is encoded in binary and contains code in machine language. Nonetheless,
it contains all the required elements (e.g., libraries) for execution.
The following command line illustrates how the riscv64-unknown-elf-ld tool,
a version of the GNU Linker3 tool that links object files generated for RISC-V ISAs,
can be invoked to link two object files together: the main.o and mylib.o object files.
In this example, the linker will produce an executable file named main.x.
Figure 3.1 illustrates the code generation process used to produce a native program
executable file from a C program organized in two files. First, the two C program files
are translated into assembly programs by the compiler. Then, the assembly programs
are assembled by the assembler, which produces object files. Finally, the linker links
the object files together producing an executable file.
Assuming the high-level language program files are named main.c and func.c,
the following sequence of commands produce a RV32I executable file named main.x.
3https://www.gnu.org/software/binutils/
i x:
2 .word 10
3
4 sum10:
5 Iw a0, x
6 addi a0, a0, 10
7 ret
Global variables and program routines are program elements that are stored on
the computer main memory. Each variable and each routine occupies a sequence of
memory words and are identified by the address of the first memory word they occupy.
At one hand, to read the contents of a global variable, or execute a routine, it suffices
to have their addresses, i.e., the address of the first memory word they occupy4 . On
the other hand, the addresses assigned to variables and routines are only final on the
executable file, after the linker links together the multiple object files into a single
file. Hence, assembly programs require a mechanism to refer to global variables and
routines. This is accomplished by using labels, as illustrated in the previous example.
In this context, before allocating space for each global variable or producing the code
for each routine, the programmer (or the compiler) defines a label that will be used
to identify the variable or the routine.
Program symbols are “names” that are associated with numerical val
ues and the “symbol table” is a data structure that maps each program
symbol to its value. Labels are automatically converted into program symbols by
the assembler and associated with a numerical value that represents its position in
the program, which is a memory address. The assembler adds all symbols to the
program’s “symbol table”, which is also stored on the object file.
We can inspect the contents of the object file by using tools that decode the infor
mation on the object file and shows them on a human-readable format, i.e., a textual
format. The GNU nm tool, for example, can be used to inspect the “symbol table”
of an object file. Assuming the previous code was encoded into an object file named
sumlO.o, we can inspect its symbol table by executing the riscv64-unknown-elf-nm
tool as follows.
$ riscv64-unknown-elf-nm sumlO.o
00000004 t .L0
00000004 t sum10
00000000 t x
4To execute a routine it suffices to set the PC with the address of the first instruction of the
routine - this is usually done by executing a “jump” instruction, which sets the PC with a given
value.
Notice that, in this case, the symbol table contains three symbols: .L05, sum10,
and x, which are associated with values 00000004, 00000004, and 00000000, respec
tively.
The programmer may also explicitly define symbols by using the .set directive.
The following code shows a fragment of assembly code that employs the .set directive
to define a symbol named answer and assign value 42 to it.
i .set answer, 42
2 get_answer:
з li a0, answer
4 ret
Assuming the previous code is stored on a program file named get answer.s, we
can assemble it and inspect the object file symbol table by executing the following
commands:
Notice that the symbol table contains two symbols: answer and get answer. The
answer symbol is an absolute symbol, i.e., its value is not changed during the linking
process - this is indicated by the letter ‘a’ on the output. The get answer symbol
is a symbol that represents a location on the .text section and may have its value
(which is an address) changed during the relocation process. The next sections discuss
the relocation process and the program sections’ concept.
i trunk42:
2 li t1, 42
з bge t1, a0, done
4 mv a0, t1
5 done:
6 ret
When assembling this program, the assembler translates each assembly instruction
(e.g., li, bge, ...) to a machine instruction, i.e., an instruction encoded with 32
bits. As a result, the program occupies a total of 16 memory words, four for each
instruction. Also, the assembler maps the first instruction to address 0, the second
one to address 4, and so on. In this context, the trunk42 label, which marks the
5 The .L0 symbol was automatically introduced by the assembler when translating the lw a0, x
assembly instruction. This is a special instruction called pseudo-instruction that will be discussed
latter on Section 6.4.
6A branch instruction is an instruction that change the execution flow under certain conditions
- In this example, the bge t1, a0, done (branch greater equal) instruction jumps to the position
identified by the done label if the value in register t1 is greater or equal to the value in register a0.
beginning of the program, is associated with address 0 and the done label, which
marks the position in which instruction ret is located, is associated with address c.
Since the bge instruction has a reference to label done, the assembler encodes the
address associated with the done label (address c) in the fields of this instruction.
The GNU objdump tool can be used to inspect several parts of the object file.
The following example shows how to use the riscv64-unknown-elf-objdump7 tool
to decode the data and instructions on the trunk.o file so that we can inspect its
contents.
$ riscv64-unknown-elf-objdump -D trunk.o
00000000 <trunk42>:
0: 02a00313 li t1,42
4: 00a35463 bge t1,a0,c <done>
8: 00030513 mv a0,t1
0000000c <done>:
c: 00008067 ret
Notice that, for each instruction, it shows its address, its encoding in hexadeci
mal, and a text that resembles assembly code8. The bge t1, a0, done instruction,
for example, is mapped to address 4 and encoded with the 32-bit value 00a35463.
The objdump tool indicates that it refers to the label done, which is mapped to ad
dress c (bge t1,a0,c <done>). Also, notice that the labels (and their addresses) are
displayed on their respective program position.
In the previous example, the trunk42 function starts at address 0, however, when
linking this object file (trunk.o) with others, the linker may need to move the code
(assign new addresses) so that they do not occupy the same addresses. In this process,
the addresses associated with labels may change and each reference to a label must
also be fixed to reflect the new addresses.
Relocation is the process in which the code and data are assigned new
memory addresses. As discussed previously, during the relocation process, the
linker needs to adjust the code and data to reflect the new addresses. More specifically,
the addresses associated with labels on the symbol table and the references to labels
must be adjusted. The relocation table is a data structure that contains
information that describes how the program instructions and data need to
be modified to reflect the addresses reassignment. Each object file contains
a relocation table and the linker uses their information to adjust the code when
performing the relocation process.
The following example shows how to use the riscv64-unknown-elf-objdump tool
to inspect the contents of the relocation table on the trunk.o file. Notice that, in this
case, the object file contains one relocation record, which indicates that the instruction
on address 4, a RISC-V branch instruction, contains a reference to label done. The
linker uses this information to adjust the branch instruction when the done label is
assigned a new address.
$ riscv64-unknown-elf-objdump -r trunk.o
00010054 <trunk42>:
10054: 02a00313 li t1,42
10058: 00a35463 bge t1,a0,10060 <done>
1005c: 00030513 mv a0,t1
00010060 <done>:
10060: 00008067 ret
Notice that the code on the trunk.x program was relocated, i.e., assigned new
addresses. In this example, the code of the trunk42 routine starts at address 10054
and the bge instruction jumps to address 10060 in case the value in register t1 is
greater or equal to the value in register a0.
The assembler assembles this program and register the exit label on the symbol
table as an undefined symbol. The riscv64-unknown-elf-nm tool identifies the un
defined symbols by placing the ‘U’ character before the symbol name. Assuming the
previous code was assembled into the main.o object file, we can inspect the contents
of its symbol table as follows:
$ riscv64-unknown-elf-nm main.o
00000000 t start
U exit
The assembler also register the reference to this symbol on the relocation table.
The riscv64-unknown-elf-objdump tool shows that the main.o object file includes
a relocation record for the reference to the exit label on the jal instruction.
9The jal instruction is used to invoke routines. In this case, it is invoking the exit routine. This
instruction will be further discussed in Section 6.7.3.
$ riscv64-unknown-elf-objdump -r main.o
When linking the object files, the linker must resolve the undefined symbols, i.e.,
it must find the symbol definition and adjust the symbol table and the code with
the symbol value. In the previous example, the linker will look for the exit symbol
so that it can adjust the jal instruction to refer to the correct address. In case it
cannot find the definition of the symbol, it stops the linking process and emits an
error message. The following example illustrates this situation. In this case, we are
trying to link the main.o file without providing another object file that contains a
definition of the exit label. Notice that the linker emits the error message undefined
reference to ‘exit’.
Assuming the code that invokes the exit function is located on the main.s file and
the exit function is located on the exit.s file, the following sequence of commands
shows how to assemble both files and link them together.
Notice that the linker is not producing the undefined reference to ‘exit’
anymore. It is worth noting that, in case the exit label is not registered as a global
label, the linker will not use it to resolve the undefined symbol and the linking process
will fail.
That the ignorance of the power of Art and Nature and such like
things, hath much advanced these foolish and impious opinions.
The opinions that we reject as foolish and impious are those we have
often named before, to wit, that those that are vulgarly accounted
Witches, make a visible and corporeal contract with the Devil, that he
sucks upon their bodies, that he hath carnal copulation with them,
that they are transubstantiated into Cats, Dogs, Squirrels, and the
like, or that they raise tempests, and fly in the air. Other powers we
grant unto them, to operate and effect whatsoever the force of
natural imagination joyned with envy, malice and vehement desire of
revenge, can perform or perpetrate, or whatsoever hurt may be done
by secret poysons and such like wayes that work by meer natural
means.
And here we are to shew the chief causes that do and have
advanced these opinions, and this principally we ascribe to mens
ignorance of the power of Nature and Art, as we shall manifest in
these following particulars.
1. There is nothing more certain than, that how great soever the
knowledge of Men be taken to be, yet the ultimate Sphere of natures
activity or ability is not perfectly known, which is made most
manifest in this, that every day there are made new discoveries of her
secrets, which prove plainly that her store is not yet totally
exhausted, nor her utmost efficiency known. And therefore those
Men must needs be precipicious, and build upon a sandy foundation,
that will ascribe corporeal effects unto Devils, and yet know not the
extent of nature, for no Man can rationally assign a beginning for
supernatural agents and actions, that does not certainly know where
the power and operation of nature ends.
2. And as it is thus in general, so in many particulars, as especially
in being ignorant of many natural agents that do work at a great
distance, and very occultly, both to help, and to hurt, as in the
weapon salve, the Sympathetick powder, the curing of diseases by
mumial applications, by Amulets, Appensions and Transplantions,
which all have been, and commonly are ascribed unto Satan, when
they are truly wrought by natural operations. And so (as we have
sufficiently manifested before) by many strange, and secret poysons
both natural and artificial, that have no bewitching power in them at
all, but work naturally, and only may be hurtful in their use through
the devilishness of some persons that use them to diverse evil ends.
3. There is nothing that doth more clearly manifest our scanted
knowledge in the secret operations of nature, and the effects that she
produceth, than the late discoveries of the workings of nature, both
in the vegetable, animal and mineral Kingdoms, brought dayly to
light by the pains and labours of industrious persons: As is most
evident in those many elucubrations, and continued discoveries of
those learned and indefatigable persons that are of the Royal Society,
which do plainly evince that hitherto we have been ignorant of
almost all the true causes of things, and therefore through blindness
have usually attributed those things to the operation of Cacodemons
that were truely wrought by nature, and thereby not smally
augmented and advanced this gross and absurd opinion of the power
of Witches.
4. Another great means in advancing De occult. Philos. l.
these Tenents hath been Mens supine 1. c. 2.
negligence in not searching into and experimenting the power of
natural agents, but resting satisfied in the sleepy notions of general
rules, and speculative Philosophy. By which means a prejudice hath
been raised against the most occult operations of nature, and natural
magick (which is (as Agrippa truly said) “The comprizer of great
power, full of most high mysteries, and containeth the most
profound contemplation, nature, power, quality, substance and
virtue of most secret things, and the knowledge of all nature) to be
condemned, as the work of the Devil and hellish fiends, which is the
handmaid and instrument of the Almighty.” And from this diabolical
pit of the ignorance of the power of nature (especially when assisted
by art) have sprung up those black and horrid lies in the mouths of
Erastus, Conringius and above all of Kircherus, denying the
possibility of the transmutation of metals, by the power of Art and
Nature, and ascribing the performance thereof by Paracelsus,
Lullius, Sendinogius and others to the Devil; so malevolent do men
grow when they are led by nescience and ignorance.
5. The ignorance of the strange and Vid. Theatr. Chym.
wonderful things that Art can bring to pass Vol. 5. p. 943.
hath been no less a cause, why the most admirable things that Art
bringeth to pass by it are through blind ignorance ascribed unto
Devils, for so have many brave learned Artists, and Mechanicians
been accused for Conjurers, as happened to Roger Bacon, Dr. Dee,
Trithemius, Cornelius Agrippa, and many others, when what they
performed was by lawful and laudable art. The strange things that
the Mathematicks and Mechanicks can perform are hardly to be
enumerated, of which were those most wonderful catoptrical glasses
mentioned by Nicero, Aquilonius, Baptista Porta and many others,
those wonderful engines in the shape of Birds, Men, Beasts, and
Fishes that do move, sing, hiss and many such like things mentioned
by Heron of Alexandria, and our Countryman Dr. Fludd; and those
that would have more ample satisfaction concerning the stupendious
things that are produced by art, may receive most large satisfaction
in reading that most learned and elaborate Epistle written as a
preface before the Book of Johannes Ernestus Burgravius called
Biolychnium vel de lampade vitæ et mortis, by Marcellus
Vranckheim Doctor of both laws, as also in reading that profound
and mysterious piece written by Roger Bacon, de admirabili
potestate artis et naturæ, et de nullitate magiæ, with the learned
notes of Dr. Dee upon it, of which he saith this: Ut videatur quod
omnis potestas magica sit inferior his operibus et indigna. And
therefore there can be nothing more unworthy, than for any man,
that pretendeth to any portion of reason, so far to dote, or suffer
himself to be led with ignorance and rashness, as to ascribe those
strange things that Nature and Art, or both joined together do
produce, unto Devils: And yet there is nothing that is more common
not only by the blind vulgar, but even by those that otherwise would
be accounted learned, and wise enough; pride and folly attendeth the
most of the Sons of Men.
6. Another gross mistake there is, in Hist.
supposing those strange things that are
performed by vaulters, tumblers, dancers upon ropes, and such like,
not possible to be done but by the assistance of the Devil, when they
are altogether brought to pass and effected by use, custome, exercise,
nimbleness and agility of body. And yet we have known some not
only of the popular rank, but many that thought themselves both
wise, learned and religious that have been so blind as to father these
things upon Devils and seriously to seem to believe, that the actors of
these things had made a league and compact with the Devil, by
whose help they performed them. And I do remember that a pretty
active young man, within these few years went about in this North
Countrey with a neat Bay Mare for money to shew tricks, which were
very odd and strange, for if she had been blindfolded, and several
pieces of money taken from several persons, and wrapped in a
cloath, the Mare would have given every one their own piece of
money; and this and many other feats she plaid, were not only by the
common people, but by others that should have been more wise,
judged to be performed by no other means but by the Devil, and
some were so stark mad as to believe and affirm that the Mare was
not a natural one, but that it was the Devil that plaid those strange
tricks in the shape of a Mare: when more sober judgments knew that
they were performed by the masters eye, and rod directing the Mare.
Error & credulitas multum in hominibus possunt.
7. In like manner are often both those that are learned, as well as
the vulgar most wofully imposed upon by the odd and strange feats
performed by Legierdemain, sleight of hand, and by wonderful
things brought to pass by subtile and cunning Impostors that act by
confederacy, and the like, of which we have given some instances
before in this treatise. And it was no evil piece of service, that Master
Scot did in his book of the discovery of Witchcraft, when he laid open
all the several tricks of Legierdemain and sleight of hand, thereby to
undeceive the ignorant multitude; and that is no less praise-worthy
that is performed by the Author of that little treatise called Hocus
Pocus junior, where all the feats are set forth in their proper colours,
so that the most ignorant may see how they are done, and that they
are miracles unknown, and but bables being discovered, which
treatise I could commend to be read of all Witchmongers and vain
credulous persons, that thereby their ignorance may be laid open,
and they convinced of their errors.
8. The ignorance or mistaking of these things, joyned with the
notions Men have imbibed from their infancy, together with
irreligious education, are the true and proper causes, that make so
many ascribe that power to Devils and Witches, that they neither
have, or ever had, or can ever bring into act. And therefore it
behoveth all that would judge aright of these abstruse matters, to
labour to understand the secret operations of nature, and the strange
works of art, to divest themselves of their false imbibed notions, and
truely and rightly to understand the Articles of the Christian Faith, to
be daily conversant in reading the Scriptures, they will then be more
fit to judge of these things, and not to call light darkness, nor
darkness light.
CHAP. XIV.
Our website is not just a platform for buying books, but a bridge
connecting readers to the timeless values of culture and wisdom. With
an elegant, user-friendly interface and an intelligent search system,
we are committed to providing a quick and convenient shopping
experience. Additionally, our special promotions and home delivery
services ensure that you save time and fully enjoy the joy of reading.
ebooknice.com