Exposition -- trying to directly answer the question
If you are reading the source code for an emulator and it is not reading certain bits of a binary (executable) file and is still faithfully executing the code, then there are three possible outcomes:
- You are wrong in thinking that the emulator does not read every bit of the file, and it in fact does, and you're simply mistaken.
- You are correct, and the emulator is not reading every single bit, because it is able to assume certain facts about the behavior of the program it's emulating in order to not need to read every single bit to know what it needs to do (perhaps because it expects a certain game engine to be run, or a certain type of graphics API, or a certain type of sound API, etc).
- You are correct, and the emulator is not reading every single bit, because there are certain bits of the executable that are simply not necessary to execute the program correctly. They may be legacy "cruft", or metadata, or anything else that is simply extra fluff that doesn't actually consist of program functionality.
- You are correct, and the emulator is not reading every single bit, because the emulator is translating certain operations in the code into higher-level operations, and is completely bypassing the low-level, processor/hardware-specific instructions. For instance, if you are asked to mimic exactly what a person is doing in a video recording of that person performing a complicated operation, and they say "Now drill a hole in the side of the box", you would be quite tempted to stop watching the video and use your existing experience of how to drill holes in things instead of following the literal motions of the guy in the video (assuming you're equipped with a proper drill and are generally experienced in life). Similarly, if the emulator can deduce that the program is asking to draw a 32x32 image on the screen at a given set of coordinates, it can stop reading the code as soon as it understands what image it is, what format the image is in, and where to draw it -- it doesn't need to see how the emulated system is drawing it.
How emulators work
An emulator that executes code for another platform and/or CPU (for instance, wine) does things at various stages. Some stages are absolutely required for the emulator to work; other stages are optional and represent possibilities for performance optimization.
Required: "Parsing" the executable code (machine code, MSIL, Java bytecode, etc.) Parsing consists of:
- Reading each bit of the executable code.
- Understanding enough of the layout/format (syntax) and purpose (semantics) of each bit/byte (or any other discrete unit of information measurement you care to use) of the native code, in order to understand what it is doing.
- To understand what a program says, the emulator has to understand the syntax of the binary format, and the semantics. Syntax consists of things like "we express 32-bit signed integers in Least Signed Bit format"; semantics consists of things like "when the native code contains an opcode 52, that means make a function call."
- Mnemonic (helps you remember why this is necessary): If I've devoted myself to following a recipe, if I totally ignore that recipe and don't even read it, it is impossible that I can ever follow that recipe, unless I randomly try a bunch of things and luck into taking the same steps that the recipe would require. Similarly, unless you have a randomized Monte Carlo simulation that executes random CPU instructions until it lucks upon performing the same functionality as the program, any emulator is going to have to understand what the program says.
Required: "Translating" the parsed-out code (usually some sort of abstract data model, state machine, abstract syntax tree, or something like that) into either high-level commands (e.g. statements in C or Java) or low-level commands (e.g. CPU instructions for an x86 processor). High-level commands tend to be more optimal. For instance, if you analyze the code flow of a long sequence of CPU instructions and determine at a high level that what it's asking for is playing a certain MP3 file from disk, you can skip the whole instruction-level emulation and just use your native platform's MP3 decoder (which may be optimized for your processor) to play the same MP3 file. On the other hand, if you were to "trace" the execution of the emulated program as literally as possible, this would be slower and less optimal, because you would be giving up much of the optimization that you benefit from by executing instructions natively.
Optional: "Optimizing" and analyzing the code flow of a large swath of the emulated program code, or the entire program, to determine the full sequence of execution, and constructing a very detailed and sophisticated model of how your emulator is going to emulate this behavior with the native platform's facilities. Wine does this to some degree, but it's helped out by the fact that the code it's translating is x86-to-x86 (meaning that in both cases the CPU is the same instruction set, so all you have to do is hook up the Windows code to the foreign UNIX-based environment and let it run "natively").
When considering the performance of an emulator, think about how many sheets of paper you'd need to write down instructions for yourself if you were watching someone on a video (with audio) baking a cake, in the following scenarios:
If you have never before in your life moved your hands or exercised any muscles in your body; (hint: you'd need thousands of sheets of paper to document the detailed steps of hand movement, hand-eye coordination, angling, velocity, position, basic techniques like grasping, holding utensils, kneading, etc.)
If you have basic motor control (you can walk and feed yourself), but have never before in your life prepared any food; (hint: you'd need tens of sheets of paper to document the individual steps, and would likely need lots of practice to get the hang of things like kneading and holding unfamiliar utensils, but you'd be able to document it in far less time than the previous case)
If you have never before in your life baked a cake, but you have done some food preparation before; (hint: you'd need a couple sheets of paper, but no more than 10; you would already be familiar with measuring ingredients, stirring, etc.)
If you have baked a cake many times before, and are very familiar with the process, but you don't know how to bake this particular variety/flavor of cake (hint: you might need half a sheet of paper to jot down the basic ingredients and the time it needs in the oven, and that's it).
Basically, at these increasing levels of "emulator competency", the emulator can do more higher-level things "natively" (using routines and procedures it already knows), and has to do less "tracing" (using routines and procedures that it is following literally from the emulated program).
To put this analogy in computer terms, you can imagine an emulator that emulates the actual hardware that the emulated program would run on, and faithfully "trace" the behavior of that hardware, perhaps even down to a hardware (circuitry) level; this would be very slow compared to an emulator that analyzes the program to such a level of sophistication that it understands when it's trying to play a sound file, and can "natively" play that sound file without needing to trace the emulated program's instructions to do so.
On "tracing" (a.k.a. rote mimicry) vs "native execution"
One last thing: tracing is slow mainly because you have to use lots of memory to "replicate" very detailed, intricate components of the thing you're emulating, and instead of just executing the instructions on your host CPU, you have to execute instructions which execute the instructions (see the level of indirection?), which leads to inefficiency. If you went whole-hog and emulated the physical hardware of a computer system as well as the program, you would be emulating the CPU, motherboard, sound card, etc., which in turn would "trace" the execution of the program as your emulator "traces" the execution of the CPU, and with this many levels of tracing, the whole thing would be extremely slow and cumbersome.
Here is a detailed example of where an emulator would not need to read every bit/byte of the input program to emulate it.
Let's say that we know of an API written in C or C++ (the details are not important) for an emulated software environment, where this API has a function
void playSound(string fileName). Let's say we know that the semantics of this function is to open up the file on disk, read its contents, figure out what encoding the file is in (MP3? WAV? something else?), then to play it out the speakers at the ordinary/expected sample rate and pitch. If we read, from the native code, a set of instructions that says "enter into the playSound routine to begin playing sound
/home/hello/foo.mp3", we can stop reading the program code right there, and use our own (optimized!) routine for natively opening that sound file and playing it. Do we need to follow the emulated processor on an instruction level? No, we really don't, not if we trust that we know what the API does.
A wild difference arises! (trouble in high-level land)
Of course, by reading a bunch of instructions and "inferring" a high-level execution plan, as in the example above, you run the risk that you might not precisely mimic the behavior of the original program running on the original hardware. Say for example, the original hardware might've had hardware limitations that only allowed it to play 8 sound files simultaneously. Well, if your new-fangled computer can play 128 sound files simultaneously just fine, and you are emulating the
playSound routine at a high level, what's to stop you from playing more than 8 sound files at a time? This could cause... strange behavior (for better or worse) in the emulated version of the program. These cases can be resolved by careful testing, or perhaps by understanding the original execution environment really well.
For example, DOSBox has a feature that lets you intentionally limit the execution speed of the emulated DOS program, because some DOS programs would run incorrectly if they were allowed to run at full speed; they actually depended on the timing of the CPU's clock rate to execute at the expected speed. This type of "feature" that intentionally limits the execution environment can be used to provide a good tradeoff between faithfulness of execution (that is, making the emulated program work properly) and efficiency of execution (that is, constructing a representation of the program that's high-level enough that it can be efficiently emulated with a minimum of tracing).