Assembly Language StepbyStep Programming with DOS and Linux 2nd Ed [Electronic resources] نسخه متنی

اینجــــا یک کتابخانه دیجیتالی است

با بیش از 100000 منبع الکترونیکی رایگان به زبان فارسی ، عربی و انگلیسی

Assembly Language StepbyStep Programming with DOS and Linux 2nd Ed [Electronic resources] - نسخه متنی

Jeff Duntemann

| نمايش فراداده ، افزودن یک نقد و بررسی
افزودن به کتابخانه شخصی
ارسال به دوستان
جستجو در متن کتاب
بیشتر
تنظیمات قلم

فونت

اندازه قلم

+ - پیش فرض

حالت نمایش

روز نیمروز شب
جستجو در لغت نامه
بیشتر
لیست موضوعات
توضیحات
افزودن یادداشت جدید









The make Utility and Dependencies


If you've done any programming in C at all, you're almost certainly familiar with the idea of the make utility. The make mechanism grew up in the C world, and although it's been adopted by many other programming languages and environments, it's never been adopted quite as thoroughly (or as nakedly) as in the C world.

What the make mechanism does is build executable program files from their component parts. Like gcc, the make utility is a puppet master that executes other programs according to a master plan, which is a simple text file called a make file. The make file (which by default is named "makefile") is a little like a computer program in that it specifies how something is to be done. But unlike a computer program, it doesn't specify the precise sequence of operations to be taken. What it does is specify what pieces of a program are required to build other pieces of the program, and in doing so ultimately defines what it takes to build the final executable file. It does this by specifying certain rules called dependencies.


Dependencies


Throughout this book we've been looking at teeny little programs with a hundred lines of code or less. In the real world, useful programs can take thousands, tens of thousands, or even millions of lines of source code. (The current release of Linux represents about 10 million lines of source code, depending on how you define what's a "part" of Linux. At last realizing that program bugs increase at least linearly with the size of a program's source code suite, Microsoft has stopped bragging about how many lines of code it took to create Windows NT. In truth, I'm not sure I want to know.) Managing such an immense quantity of source code is the central problem in software engineering. Making programs modular is the oldest and most-used method of dealing with program complexity. Cutting up a large program into smaller chunks and working on the chunks separately helps a great deal. In ambitious programs, some of the chunks are further cut into even smaller chunks, and sometimes the various chunks are written in more than one programming language. Of course, that creates the additional challenge of knowing how the chunks are created and how they all fit together. For that you really need a blueprint.

A make file is such a blueprint.

In a modular program, each chunk of code is created somehow, generally by using a compiler or an assembler and a linker. Compilers, assemblers, and linkers take one or more files and create new files from them. An assembler, as you've learned, takes a .asm file full of assembly language source code and uses it to create a linkable object code file or (in some cases) an executable program file. You can't create the object Figure 12.1 and the associated discussion if it's still fuzzy as to why a C compiler is required to link an assembly program.) The dependency itself can be pretty simply stated:


eatlinux: eatlinux.o

All this says is that to generate the executable file eatlinux, we first need to have the file eatlinux.o. The line is actually a dependency line written as it should be for inclusion in a make file. In any but the smallest programs (such as this one) the linker will have to link more than one .o file. So this is probably the simplest possible sort of dependency: One executable file depends on one object code file. If there are additional files that must be linked to generate the executable file, these are placed in a list, separated by spaces:


linkbase: linkbase.o linkparse.o linkfile.o

This line tells us that the executable file linkbase depends on three object code files, and all three of these files must exist before we can generate the executable file that we want.

Lines like these tell us what files are required, but not what must be done with them. That's an essential part of the blueprint, and it's handled in a line that follows the dependency line. The two lines work together. Here's both lines for our simple example:


eatlinux: eatlinux.o
gcc eatlinux.o -o eatlinux

The second line is indented by custom. The two lines together should be pretty easy to understand: The first line tells us what file or files are required to do the job. The second line tells us how the job is to be done: in this case, by using gcc to link eatlinux.o into the executable file eatlinux.

Nice and neat: We specify which files are necessary and what has to be done with them. The make mechanism, however, has one more very important aspect: knowing whether the job as a whole actually has to be done at all.


When a File Is Up to Date


It may seem idiotic to have to come out and say so, but once a file has been compiled or linked, it's been done, and it doesn't have to be done again . . . until we modify one of the required source or object code files. The make utility knows this. It can tell when a compile or a link task needs to be done at all, and if the job doesn't have to be done, make will refuse to do it.

How does make know if the job needs doing? Consider this dependency:


eatlinux: eatlinux.o

Make looks at this and understands that the executable file eatlinux depends on the object code file eatlinux.o, and that you can't generate eatlinux without having eatlinux.o. It also knows when both files were last changed, and if the executable file eatlinux is newer than eatlinux.o, it deduces that any changes made to eatlinux.o are already reflected in eatlinux. (It can be absolutely sure of this because the only way to generate eatlinux is by processing eatlinux.o.)

The make utility pays close attention to Linux timestamps. Whenever you edit a source code file, or generate an object code file or an executable file, Linux updates that file's timestamp to the moment that the changes were finally completed. And even though you may have created the original file six months ago, by convention we say that a file is newer than another if the time value in its timestamp is more recent than that of another file, even one that was created only 10 minutes ago.

(In case you're unfamiliar with the notion of a timestamp, it's simply a value that an operating system keeps in a file system directory for every file in the directory. A file's timestamp is updated to the current clock time whenever the file is changed.)

When a file is newer than all of the files that it depends upon (according to the dependencies called out in the make file), that file is said to be up to date. Nothing will be accomplished by generating it again, because all information contained in the component files is reflected in the dependent file.


Chains of Dependencies


So far, this may seem like a lot of fuss to no great purpose. But the real value in the make mechanism begins to appear when a single make file contains chains of dependencies. Even in the simplest make files, there will be dependencies that depend on other dependencies. Our completely trivial example program requires two dependency statements in its make file.

Consider that the following dependency statement specifies how to generate an executable file from an object code (.o) file:


eatlinux: eatlinux.o
gcc eatlinux.o -o eatlinux

The gist here is that to make eatlinux, you start with eatlinux.o and process it according to the recipe in the second line. Okay, . . . so where does eatlinux.o come from? That requires a second dependency statement:


eatlinux.o: eatlinux.asm
nasm -f elf eatlinux.asm

Here we explain that to generate eatlinux.o, we need eatlinux .asm . . . and to generate it we follow the recipe in the second line. The full makefile would contain nothing more than these two dependencies:


eatlinux: eatlinux.o
gcc eatlinux.o -o eatlinux
eatlinux.o: eatlinux.asm
nasm -f elf eatlinux.asm


These two dependency statements define the two steps that we must take to generate an executable program file from our very simple assembly language source code file eatlinux.asm. However, it's not obvious from the two dependencies I show here that all the fuss is worthwhile. Assembling eatlinux.asm pretty much requires that we link eatlinux.o to create eatlinux. The two steps go together in virtually all cases.

But consider a real-world programming project, in which there are hundreds of separate source code files. Only some of those files might be "on the rack" and undergoing change on any given day. However, to build and test the final program, all of the files are required. But . . . are all the compilation steps and assembly steps required? Not at all.

An executable program is knit together by the linker from one or more-often many more-object code files. If all but (let's say) two of the object code files are up to date, there's no reason to compile the other 147 source code files. You just compile the two source code files that have been changed, and then link all 149 object code files into the executable.

The challenge, of course, is correctly remembering which two files have changed-and being sure that all changes that have been recently made to any of the 149 source code files are reflected in the final executable file. That's a lot of remembering, or referring to notes. And it gets worse when more than one person is working on the project, as will be the case in nearly all commercial software development projects. The make utility makes remembering any of this unnecessary. Make figures it out and does only what must be done, no more, no less.

The make utility looks at the make file, and it looks at the timestamps of all the source code and object code files called out in the make file. If the executable file is newer than all of the object code files, nothing needs to be done. However, if any of the object code files are newer than the executable file, the executable file must be relinked. And if one or more of the source code files are newer than either the executable file or their respective object code files, some compiling must be done before any linking is done.

What make does is start with the executable file and look for chains of dependency moving away from that. The executable file depends on one or more object files, which depend on one or more source code files, and make walks the path up the various chains, taking note of what's newer than what and what must be done to put it all right. Make then executes the compiler, assembler, and linker selectively to be sure that the executable file is ultimately newer than all of the files that it depends on. Make ensures that all work that needs to be done gets done. Furthermore, make avoids spending unnecessary time compiling and assembling files that are already up to date and do not need to be compiled or assembled. Given that a full build (by which I mean the recompilation and relinking of every single file in the project) can take several hours on an ambitious program, make saves an enormous amount of idle time when all you need to do is test changes made to one small part of the program.

There is actually a lot more to the Unix make facility than this, but what I've described are the fundamental principles. You have the power to make compiling conditional, inclusion of files conditional, and much more. You won't need to fuss with such things on your first forays into assembly language (or C programming, for that matter), but it's good to know that the power is there as your programming skills improve and you take on more ambitious projects.


Using make from within EMACS


The EMACS source code editor has the power to invoke the make facility without forcing you to leave the editor. This means that you can change a source code file in the editor and then compile it without dropping back out to the Linux shell. EMACS has a command called Compile, which is an item in its Tools menu. When you select Tools | Compile, EMACS will place the following command in the command line at the bottom of its window and wait for you to do something:


compile command: make -k

You can add additional text to this command line, you can backspace over it and delete parts of it (like the -k option), or you can press Enter and execute the command as EMACS wrote it. In most cases (especially while you're just getting started) all you need to do is press Enter.

Here's what happens: EMACS invokes the make utility. Unless you typed another name for the make file, make assumes that the make file will be called "makefile." The -k option instructs make to stop building any file in which an error occurs and to leave the previous copy of the target file undisturbed. If this doesn't make sense to you right now, don't worry-it's a good idea to use -k until you're really sure you don't need to. EMACS places it on the command line automatically, and you have to explicitly backspace over it to make it go away.

When it invokes make, EMACS opens a new text buffer and pipes all text output from the make process into that buffer. It will typically split your EMACS window so that the make buffer window is below the buffer you were in when you selected Tools | Compile. This allows you to see the progress of the make operation (including any error or warning messages) without leaving EMACS.

Of course, if make determines that the executable file is up to date, it will do nothing beyond displaying a message to that effect:


make: 'eatlinux' is up to date.

If you're using EMACS in an X Window window (which is what will happen automatically if you have X Window running when you invoke EMACS), you can switch from window to window by clicking with the mouse on the window you want to work in. This way you can click your way right back to the window in which you're editing source code.

One advantage to having make pipe its output into an EMACS buffer is that you can save the buffer to a text file for later reference. To do this, just keep the cursor in the make window, select the Files | Save Buffer As command, and then give the new buffer file a name.


/ 166