ES Unit 5
ES Unit 5
The hardware components within an embedded system can only directly transmit, store,
and execute machine code, a basic language consisting of ones and zeros. Machine code
was used in earlier days to program computer systems, which made creating any complex
application a long and tedious ordeal. In order to make programming more efficient,
machine code was made visible to programmers through the creation of a hardware-
specific set of instructions, where each instruction corresponded to one or more machine
code operations. These hardware- specific sets of instructions were referred to as
assembly language.
Over time, other programming languages, such as C, C++, Java, etc., evolved with
instruction sets that were (among other things) more hardware-independent. These are
commonly referred to as high level languages because they are semantically further away
from machine code, they more resemble human languages, and are typically independent
of the hardware. This is in contrast to a low-level language, such as assembly language,
which more closely resembles machine code. Unlike high-level languages, low-level
languages are hardware dependent, meaning there is a unique instruction set for
processors with different architectures. Table outlines this evolution of programming
languages. Because machine code is the only language the hardware can directly execute,
all other languages need some type of mechanism to generate the corresponding machine
code. This mechanism usually includes one or some combination of preprocessing,
translation, and interpretation. Depending on the language, these mechanisms exist on the
programmer‘s host system (typically a non embedded development system, such as a PC
or Sparc station), or the target system (the embedded system being
developed)..Preprocessing is an optional step that occurs before either the translation or
interpretation of source code, and whose functionality is commonly implemented by a
preprocessor.
The preprocessor‘s role is to organize and restructure the source code to make translation
or interpretation of this code easier. As an example, in languages like C and C++, it is a
preprocessor that allows the use of named code fragments, such as macros, that simplify
code development by allowing the use of the macro‘s name in the code to replace
fragments of code. The preprocessor then replaces the macro name with the contents of
the macro during preprocessing.
The preprocessor can exist as a separate entity, or can be integrated within the translation
or interpretation unit. Many languages convert source code, either directly or after having
been preprocessed through use of a compiler, a program that generates a particular target
language—such as machine code and Java byte code—from the source language. A
compiler typically ―translates‖ all of the source code to some target code at one time. As
is usually the case in embedded systems, compilers are located on the programmer‘s host
machine and generate target code for hardware platforms that differ from the platform the
compiler is actually running on. These compilers are commonly referred to as cross-
compilers.
DeAssember
It is concise.
It is structured
It doesn't require developers to know the assembly language.
It recognizes and converts low level idioms into high level notions.
It is less confusing and therefore easier to understand.
It is less repetitive and less distracting.
It uses data flow analysis.
Usually the decompiler's output is five to ten times shorter than the disassembler's
output. For example, a typical modern program contains from 400KB to 5MB of
binary code. The disassembler's output for such a program will include around 5-
100MB of text, which can take anything from several weeks to several months to
analyze completely. Analysts cannot spend this much time on a single program for
economic reasons. The decompiler's output for a typical program will be from 400KB
to 10MB. Although this is still a big volume to read and understand (about the size of
a thick book), the time needed for analysis time is divided by 10 or more. The second
big difference is that the decompiler output is structured. Instead of a linear flow of
instructions where each line is similar to all the others, the text is indented to make
the program logic explicit. Control flow constructs such as conditional statements,
loops, and switches are marked with the appropriate keywords.The decompiler's
output is easier to understand than the disassembler's output because it is high level.
To be able to use a disassembler, an analyst must know the target processor's
assembly language. Mainstream programmers do not use assembly languages for
everyday tasks, but virtually everyone uses high level languages today. Decompilers
remove the gap between the typical programming languages and the output language.
More analysts can use a decompiler than a disassembler. Decompiler
outputDecompilers convert assembly level idioms into high-level abstractions. Some
idioms can be quite long and time consuming to analyze. The following one line code
x = y / 2; can be transformed by the compiler into a series of 20-30 processor
instructions. It takes at least 15-30 seconds for an experienced analyst to recognize
the pattern and mentally replace it with the original line.. If the code includes many
such idioms, an analyst is forced to take notes and mark each pattern with its short
representation. All this slows down the analysis tremendously.
Decompilers remove this burden from the analysts. The amount of assembler
instructions to analyze is huge. They look very similar to each other and their patterns
are very repetitive. Reading disassembler output is nothing like reading a captivating
story. In a compiler generated program 95% of the code will be really boring to read
and analyze. It is extremely easy for an analyst to confuse two similar looking
snippets of code, and simply lose his way in the output. These two factors (the size
and the boring nature of the text) lead to the following phenomenon: binary programs
are never fully analyzed. Analysts try to locate suspicious parts by using some
heuristics and some automation tools. Exceptions happen when the program is
extremely small or an analyst devotes a disproportionally huge amount of time to the
analysis. Decompilers alleviate both problems: their output is shorter and less
repetitive. The output still contains some repetition, but it is manageable by a human
being. Besides, this repetition can be addressed by automating the analysis. Repetitive
patterns in the binary code call for a solution. One obvious solution is to employ the
computer to find patterns and somehow reduce them into something shorter and
easier for human analysts to grasp. Some disassemblers (including IDA Pro) provide
a means to automate analysis. However, the number of available analytical modules
stays low, so repetitive code continues to be a problem. The main reason is that
recognizing binary patterns is a surprisingly difficult task. Any ―simple‖ action,
including basic arithmetic operations such as addition and subtraction, can be
represented in an endless number of ways in binary form. The compiler might use the
addition operator for subtraction and vice versa. It can store constant numbers
somewhere in its memory and load them when needed. It can use the fact that, after
some operations, the register value can be proven to be a known constant, and just use
the register without reinitializing it. The diversity of methods used explains the small
number of available analytical modules.
Decompilers
Decompilers remove the gap between the typical programming languages and the
output language. More analysts can use a decompiler than a disassembler.
Decompilers convert assembly level idioms into high-level abstractions. Some idioms
can be quite long and time consuming to analyze. The following one line code x = y /
2; can be transformed by the compiler into a series of 20-30 processor instructions. It
takes at least 15-30 seconds for an experienced analyst to recognize the pattern and
mentally replace it with the original line.. If the code includes many such idioms, an
analyst is forced to take notes and mark each pattern with its short representation. All
this slows down the analysis tremendously. Decompilers remove this burden from the
analysts. The amount of assembler instructions to analyze is huge. They look very
similar to each other and their patterns are very repetitive. Reading disassembler
output is nothing like reading a captivating story. In a compiler generated program
95% of the code will be really boring to read and analyze. It is extremely easy for an
analyst to confuse two similar looking snippets of code, and simply lose his way in
the output. These two factors (the size and the boring nature of the text) lead to the
following phenomenon: binary programs are never fully analyzed. Analysts try to
locate suspicious parts by using some heuristics and some automation tools.
Exceptions happen when the program is extremely small or an analyst devotes a
disproportionally huge amount of time to the analysis. Decompilers alleviate both
problems: their output is shorter and less repetitive. The output still contains some
repetition, but it is manageable by a human being. Besides, this repetition can be
addressed by automating the analysis. Repetitive patterns in the binary code call for a
solution. One obvious solution is to employ the computer to find patterns and
somehow reduce them into something shorter and easier for human analysts to grasp.
Some disassemblers (including IDA Pro) provide a means to automate analysis.
However, the number of available analytical modules stays low, so repetitive code
continues to be a problem. The main reason is that recognizing binary patterns is a
surprisingly difficult task. Any ―simple‖ action, including basic arithmetic
operations such as addition and subtraction, can be represented in an endless number
of ways in binary form. The compiler might use the addition operator for subtraction
and vice versa. It can store constant numbers somewhere in its memory and load them
when needed. It can use the fact that, after some operations, the register value can be
proven to be a known constant, and just use the register without reinitializing it. The
diversity of methods used explains the small number of available analytical modules.
The situation is different with a decompiler. Automation becomes much easier
because the decompiler provides the analyst with high level notions. Many patterns
are automatically recognized and replaced with abstract notions. The remaining
patterns can be detected easily because of the formalisms the decompiler introduces.
For example, the notions of function parameters and calling conventions are strictly
formalized. Decompilers make it extremely easy to find the parameters of any
function call, even if those parameters are initialized far away from the call
instruction. With a disassembler, this is a daunting task, which requires handling each
case individually. Decompilers, in contrast with disassemblers, perform extensive
data flow analysis on the input. This means that questions such as, ―Where is the
variable initialized?‖ and, ―Is this variable used?‖ can be answered immediately,
without doing any extensive search over the function. Analysts routinely pose and
answer these questions, and having the answers immediately increases their
productivity. Two reasons: 1) they are tough to build because decompilation theory is
in its infancy; and 2) decompilers have to make many assumptions about the input
file, and some of these assumptions may be wrong. Wrong assumptions lead to
incorrect output. In order to be practically useful, decompilers must have a means to
remove incorrect assumptions and be interactive in general. Building interactive
applications is more difficult than building offline (batch) applications. In short, these
two obstacles make creating a decompiler a difficult endeavor both in theory and in
practice.Given all the above, we are proud to present our analytical tool, the Hex-
Rays Decompiler. It embodies almost 10 years of proprieary research and implements
many new approaches to the problems discussed above. The highlights of our
decompiler are:
The roles of simulation and emulation in the development of DSP-based designs can be
confusing, since at a glance they perform similar functions. In simplest terms, the main
difference between simulation and emulation is that simulation is done all in software and
emulation is done in hardware. Probe deeper, however, and the unique characteristics and
compelling benefits of each tool are clear. Together they complement each other to
deliver benefits that either one alone cannot provide.
Traditionally, the work of simulation begins in the very first stages of design, where the
designer uses it to evaluate initial code. Developers use simulators to model the
architecture of complex multi-core systems early in the design process, typically months
before hardware is available. This makes it possible to evaluate various design
configurations without the need for prototype devices. In addition, the simulation
software collects massive amounts of data as the designer runs their core code and makes
different variations to it. The simulation software also makes it possible to determine the
most efficient code to use in the application´s design by representing the performance of
the DSP and any peripherals, which affect the performance of the code.
However, in the past, the slowness of the simulators prevented them from being used
extensively. To be effective, simulators must be fast to allow for the massive data
collection needed for complex DSP applications. As a result of the slow simulators,
designers resort instead to conducting tuning and analysis later in the development cycle
when hardware prototypes are available – a process that results in considerable time and
cost penalties. With the introduction of fast simulation technology and data collection
tools, developers can gather huge amounts of data in minutes instead of the hours
previous or competitive simulators required. Simulators are an important tool in the
design and debug process because they can run a simulation identically over and over,
which hardware-based evaluations cannot achieve because of changes caused by external
events, such as interrupts. They are also extremely flexible and provide insight into the
CPU alone or can be used to model a full system. They also can be easily rescaled and
integrated with different memories and peripherals. Since designers are modeling the
hardware they can actually build things into the model that allow them to extract a lot
more data enabling some of the advanced analysis capabilities.
Target board hardware and software later copied to get the final embedded system
Final system function exactly as the one tested and debugged and finalized during
the development process
Host system at PC or workstation or laptop
High performance processor with caches, large RAM memory
keyboard
display monitor
mice
network connection
compiler
Cross assembler
Source code is typically written with a tool such as a standard ASCII text editor, or an
Integrated Development Environment (IDE) located on the host (development) platform,
as shown in Figure. An IDE is a collection of tools, including an ASCII text editor,
integrated into one application user interface. While any ASCII text editor can be used to
write any type of code, independent of language and platform, an IDE is specific to the
platform and is typically provided by the IDE‘s vendor, a hardware manufacturer (in a
starter kit that bundles the hardware board with tools such as an IDE or text editor), OS
vendor, or language vendor (Java, C, etc.).
Translating code was along with a brief introduction to some of the tools used in
translating code, including preprocessors, interpreters, compilers, and linkers. As a
review, after the source code has been written, it needs to be translated into machine
code, since machine code is the only language the hardware can directly execute. All
other languages need development tools that generate the corresponding machine code
the hardware will understand. This mechanism usually includes one or some combination
of preprocessing, translation, and/or interpretation machine code generation techniques.
These mechanisms are implemented within a wide variety of translating development
tools. Preprocessing is an optional step that occurs either before the translation or
interpretation of source code, and whose functionality is commonly implemented by a
preprocessor. The preprocessor‘s role is to organize and restructure the source code to
make translation or interpretation of this code easier. The preprocessor can be a separate
entity, or can be integrated within the translation or interpretation unit.
Many languages convert source code, either directly or after having been preprocessed, to
target code through the use of a compiler, a program which generates some target
language, such as machine code, Java byte code, etc., from the source language, such as
assembly, C, Java, etc
A compiler typically translates all of the source code to a target code at one time. As is
usually the case in embedded systems, most compilers are located on the programmer‘s
host machine and generate target code for hardware platforms that differ from the
platform the compiler is actually running on. These compilers are commonly referred to
as cross-compilers. In the case of assembly, an assembly compiler is a specialized cross-
compiler referred to as an assembler, and will always generate machine code. Other high-
level language compilers are commonly referred to by the language name plus
―compiler‖ (i.e., Java compiler, C compiler). High-level language compilers can vary
widely in terms of what is generated. Some generate machine code while others generate
other high-level languages, which then require what is produced to be run through at least
one more compiler. Still other compilers generate assembly code, which then must be run
through an assembler. After all the compilation on the programmer‘s host machine is
completed, the remaining target code file is commonly referred to as an object file, and
can contain anything from machine code to Java byte code, depending on the
programming language used. As shown in Figure , a linker integrates this object file with
any other required system libraries, creating what is commonly referred to as an
executable binary file, either directly onto the board‘s memory or ready to be transferred
to the target embedded system‘s memory by a loader.
Debugging Tools
Aside from creating the architecture, debugging code is probably the most difficult task
of the development cycle. Debugging is primarily the task of locating and fixing errors
within the system. This task is made simpler when the programmer is familiar with the
various types of debugging tools available and how they can be used (the type of
information shown in Table). As seen from some of the descriptions in Table, debugging
tools reside and interconnect in some combination of standalone devices, on the host,
and/or on the target board.
Some of these tools are active debugging tools and are intrusive to the running of the
embedded system, while other debug tools passively capture the operation of the system
with no intrusion as the system is running. Debugging an embedded system usually
requires a combination of these tools in order to address all of the different types of
problems that can arise during the development process.
Quality Assurance and Testing of the Design
Among the goals of testing and assuring the quality of a system are finding bugs within a
design and tracking whether the bugs are fixed. Quality assurance and testing is similar to
debugging, discussed earlier in this chapter, except that the goals of debugging are to
actually fix discovered bugs. Another main difference between debugging and testing the
system is that debugging typically occurs when the developer encounters a problem in
trying to complete a portion of the design, and then typically tests-to-pass the bug fix
(meaning tests only to ensure the system minimally works under normal circumstances).
With testing, on the other hand, bugs are discovered as a result of trying to break the
system, including both testing-to-pass and testing-to-fail, where weaknesses in the system
are probed. Under testing, bugs usually stem from either the system not adhering to the
architectural specifications— i.e., behaving in a way it shouldn‘t according to
documentation, not behaving in a way it should according to the documentation,
behaving in a way not mentioned in documentation— or the inability to test the system.
The types of bugs encountered in testing depend on the type of testing being done. In
general, testing techniques fall under one of four models: static black box testing, static
white box testing, dynamic black box testing, or dynamic white box testing (see the matrix
in Figure 12-9). Black box testing occurs with a tester that has no visibility into the
internal workings of the system (no schematics, no source code, etc.). Black box testing is
based on general product requirements documentation, as opposed to white box testing
(also referred to clear box or glass box testing) in which the tester has access to source
code, schematics, and so on. Static testing is done while the system is not running,
whereas dynamic testing is done when the system is running.
Within each of the models, testing can be further broken down to include unit/module
testing (incremental testing of individual elements within the system), compatibility
testing (testing that the element doesn‘t cause problems with other elements in the
system), integration testing (incremental testing of integrated elements), system testing
(testing the entire embedded system with all elements integrated), regression testing
(rerunning previously passed tests after system modification), and manufacturing testing
(testing to ensure that manufacturing of system didn‘t introduce bugs), just to name a
few. From these types of tests, an effective set of test cases can be derived that verify that
an element and/or system meets the architectural specifications, as well as validate that
the element and/or system meets the actual requirements, which may or may not have
been reflected correctly or at all in the documentation. Once the test cases have been
completed and the tests are run, how the results are handled can vary depending on the
organization, but typically vary between informal, where information is exchanged
without any specific process being followed, and formal design reviews, or peer reviews
where fellow developers exchange elements to test, walkthroughs where the responsible
engineer formally walks through the schematics and source code, inspections where
someone other than the responsible engineer does the walk through, and so on. Specific
testing methodologies and templates for test cases, as well as the entire testing process,
have been defined in several popular industry quality assurance and testing standards,
including ISO9000 Quality Assurance standards, Capability Maturity Model (CMM), and
the ANSI/IEEE 829 Preparation, Running, and Completion of Testing standards.
Embedded Hardware Tests
Testing codes for the GUIs, and HCIs Testing the codes for the tasks
Testing of codes for decision blocks in the tasks
Testing of codes for the loops
Testing of codes for display
Testing of codes for communication to other computing systems
Testing Steps at Host Machine
1. Initial Tests─ each module or segment at initial stage itself and on host itself
2. Test data─ all possible combinations of data designed and taken as test data
5. Tests-2:scaffold software, software running on host the target dependent codes and
which have same start of code and port and device addresses as at the hardware.
Instructions– given from file or keyboard inputs. Outputs–at host’s LCD display and
save at file
9. Assert-Macro tests─ insert the codes in the program that check whether a condition or
a parameter actually turns true or false. If it turns false─ the program stops. Use the
assert macro at different critical places in the application program
Laboratory Tools
Volt-Ohm meter─
Useful for checking the power supply voltage at source and voltage levels at chips power
input pins, and port pins initial at start and final voltage levels after the software run,
checking broken connections, improper ground connections, and burnout resistances and
diodes.
Logic Probe
Oscilloscope
Logic Analyser
Bit Rate meter
Use of Logic Probe
A powerful hardware tool for checking multiple lines carrying address, data and
control bits, IO buses, ports, peripherals and clock
Recognizes only discrete voltage conditions, '1' and '0'.
Collects, stores and tracks multiple signals and bus transactions simultaneously
and successively.
Reads multiple input lines (24 or 48) and later displays, using this tool, each
transaction on each of these on computer monitor (screen) observed
Use of Bit rate meter
A measuring device that finds numbers of ‘1’s and ‘0’ in the preselected time
spans. • Measures throughput.
can estimate bits ‘1’s and ‘0’s in a test message and then use bit rate meter to
find whether that matches with the message.