This document describes porting SCO OpenServer Release 5 applications to Release 6, and techniques that can be used to make such a port successful.
Porting applications to SCO OpenServer 6 is not difficult. Many ports will involve no more than making a few changes to a makefile, then recompiling and using the SCO OpenServer 6 Development System (Dev Sys).
Porting may not even be necessary:
But you may want to recompile your application on SCO OpenServer 6 to take advantage of new features such as multithreading or large files, to gain better runtime performance, to access new resources on Release 6, or for other reasons.
The SCO OpenServer 6 Development System allows you to compile programs using either the Release 5 ABI or the Release 6 ABI, as described in the following section.
SCO OpenServer 6 is designed to run both SCO OpenServer Release 5 (OSR5) ABI applications as well as System V Release 5 (SVR5) applications. These two kinds of applications are distinct because they:
The OSR5 ABI is the interface supported by SCO OpenServer Release 5 systems, while the SVR5 ABI is the default ABI for Release 6 (and for future OpenServer development). It is also the ABI supported by the UnixWare/OpenServer Development Kit (UDK) and so is sometimes called the UDK ABI.
Within any one operating system process, every object file must have been compiled using either the OSR5 ABI or the SVR5 ABI; this applies to the application and all the libraries it uses. These ABIs cannot be mixed within a single process.
The C compiler (cc command), the C++ compiler (CC command), and a few other development tools includes a -K option to select the ABI (or mode), with udk representing the SVR5 ABI and osr specifying the OSR5 ABI.
The
default ABI used (i.e., when no -K option is specified) depends upon
how the compilers and related tools are invoked. For example:
/usr/ccs/bin/cc
default is UDK mode; use
-K osr for OSR5 mode (OSR5 ABI)
/osr5/usr/ccs/bin/cc
default is OSR mode; use
-K udk for UDK mode (SVR5 ABI)
In general, which ABI you use when you port your application to Release 6, depend on the considerations listed in the following table:
If you have an existing SCO OpenServer 5 application that you want to modernize with threads or large files or some other SVR5-dependent feature, you must compile it in UDK mode. Because UDK mode features some different APIs from SCO OpenServer 5, it is possible that you will have some porting work and source code modifications to do. Occasionally the mode requirements may present a contradiction: for instance, you have an existing SCO OpenServer 5 application program that depends upon and is linked against a third-party library archive. Now you want to modify another part of this program to handle large files. That means you need to use UDK mode, but the presence of the third-party library means you need to use OSR mode. Assuming you cannot get the library vendor to re-build it in UDK mode, what do you do?
In this situation you must split your application program into two processes. One process makes the calls to the third-party library and is compiled in OSR5 mode. The other process makes the calls to do the large file I/O and is compiled in UDK mode. Then you must use some sort of inter-process mechanism (such as sockets, pipes, or IPC) to communicate between the two processes.
There are two sets of tools that will be available for development:
The Dev Sysis found on SCO OpenServer 6 Installation CD-ROM in the media kit. The GNU GCC tools are part of the "Open Source Tool Kit" (containing pre-built, fully packaged versions of these free or GPL software), which will be made available soon after SCO OpenServer 6 is released and will be officially supported by SCO.
If a lot of nonstandard GNU extensions are used in the source code, the GNU tools represent the path of least resistance. Otherwise, using the OSR6 Dev Sys will probably produce the best final results.
This document assumes that the SCO OpenServer 6 Dev Sys is being used, although some of the material also applies to the case where GNU tools are being used.
Finally, if your application is in Java, then the Java 2 Standard Edition for SCO UNIX Operating Systems is the vehicle to use to build (if necessary) and run the application. This is installed by default on every SCO OpenServer 6 system.
When you start porting an existing application you typically have a bunch of source files and a makefile (or some kind of script that tells how to compile and build the application).
Examine the makefile (if one is present). If it is named GNUmakefile, or contains conditional rules such as:
ifeq ($(VARIANT), DBG) CFLAGS_COMMON += -DJCOV endif
This means it it a GNU makefile, and may includes a number of extensions that the standard UNIX (including SCO OpenServer 6) make command does not support. If you are unsure, try running it through the SCO OpenServer 6 make; a GNU makefile will generate large numbers of errors.
If this is the case, get the GNU make (sometimes referred to as gmake) from the SCO OpenServer 6 Open Source Tool Kit. Do this even if you are using the SCO Dev Sys tools for compiling and linking so you can avoid rewriting the entire makefile.
If you are porting an open source project, you may find that the makefile itself is generated by a configure script, which itself is generated by tools such as autoconf and automake. These tools are provided in the SCO OpenServer 6 Open Source Tool Kit. You can use the Dev Sys in the presence of configure scripts (typically you override the CC and CXX settings when doing the initial configure), but this is usually a good reason to use the GNU GCC compiler.
Basic compiler options are defined by POSIX and tend to be the same across platforms (such as -c and -P). Others are trickier and can lead to porting problems. Here are some of the more common problems:
[ -lresolv ] -lsocket -lnsl
In general, SCO OpenServer 6 is compliant with POSIX, XPG4, UNIX 95, and UNIX 98. If the application being ported is also compliant to these standards, a simple recompile and relink should be all that is necessary. In reality, things are rarely that easy. Kernel and API compatibility issues may affect the ability of your application to run as expected on Release 6. These issues are described in the section "Kernel and API Compatibility Notes" on page 23.
int length(void);
int foo(void)
{
int x=length();
char f[x];
...
}
The ANSI/ISO 1998 C++ standard invalidated a lot of old, existing C++ code. The following are a few of the better-known examples:
A more detailed discussion of some of these incompatibilities can be found in newer C++ textbooks, such as Bjarne Stroustrup's The C++ Programming Language (Third Edition), or in the Internet McCluskey C++ Newsletter.
So how do you get existing C++ code bases to compile using the Dev Sys compiler? For many years the original AT&T cfront was the most heavily used C++ compiler. For existing cfront-built code, there is a Dev Sys CC -Xo compatibility option that provides cfront source compatibility. But this also enables cfront bug tolerance and many anachronisms. There are some instances of cfront that CC -Xo will not detect or accept. It is intended as a transitional tool for converting code to the modern dialect of C++ acceptable by the Dev Sys C++ compiler. It should not be used on a permanent basis.
For example, consider this code:
template <class T>
class A {
int m, n;
int explicit;
public:
void f();
T g();
};
template <class T>
void A<T>::f() {
for (int i = 0; i < 10; i++)
n += i;
for (i = 0; i < 10; i++)
m += i;
}
char A<char>::g() { return `a'; }
int main() {
A<int> a;
a.f();
}
This compiles under a cfront-era C++ compiler without problems, but generates many errors with the Dev Sys C++ compiler. The same code source compiles without error under the Dev Sys C++ compiler using the -Xo option.
Most ANSI/ISO source language incompatibilities are not complicated and can be straightforwardly resolved, so in most cases it is best to bring the code up to date. In this case, it's simply a matter of avoiding the new keyword, using the new template specialization syntax, and adjusting to the new for loop variable scope rule.
Here is the new version with three simple edits that compiles properly on the OSR6 Dev Sys C++ compiler without using any compatibility option:
template <class T>
class A {
int m, n;
int is_explicit; // changed
public:
void f();
T g();
};
template <class T>
void A<T>::f() {
for (int i = 0; i < 10; i++)
n += i;
for (int i = 0; i < 10; i++) // changed
m += i;
}
template<> char A<char>::g() { return `a'; } // changed
int main() {
A<int> a;
a.f();
}
The subsections that follow discuss changes to the runtime environment in SCO OpenServer Release 6 that must be taken into account when porting any Release 5 application. Your application may also encounter runtime issues related to other changes in the runtime environment between Release 5 and Release 6. See the Upgrade Guide at http://www.sco.de/produkte/ for more information on changes to the runtime environment in Release 6.
If a Release 5 application requires Release 5 runtime commands (that is, it fails using the default command reached by the default PATH variable on OpenServer 6), Release 5 commands are supplied in the directory /osr5/bin. Change the PATH variable for the application so that it accesses binaries under /osr5/bin before other command directories. Note that manpages for these commands are not provided on the system; access them on the OpenServer 507 doc web site..
Although links have been used to minimize problems associated with changes of location of system files accessed by applications at runtime, some applications that depend on the internal format of files may experience problems interpreting file contents. One example is the default /bin/cpio command, which uses an "Extended cpio File Format", instead of the "ASCII cpio format" used by the cpio command on OpenServer 5. If your application requires the OpenServer 5 cpio file format, then you'll need to change your application to access the /osr5/bin/cpio version of cpio as described in the last section.
NETbios/NETbeui are no longer supported; runtime calls to NETbios commands and references to NETbios resources will fail on SCO OpenServer Release 6.
Calls to the legacy i286emul/x286emul runtime emulators and references to associated resources will fail on OpenServer 6.
SCO OpenServer Release 6 includes a number of tools for debugging. The sections that follow discuss some of these features in more detail.
SCO OpenServer 6 includes a useful command named truss that can trace the system calls made by an application. Using truss does not require recompilation, relinking, access to the source code, or even a symbol table. The truss command replaces the Release 5 trace and scotruss commands.
$ cat fstab.c
#includes <stdio.h>
int main() {
FILE* fp = fopen("/etc/fstab", "r");
if (!fp)
fprintf(stderr, "*** cannot open file\n");
fclose(fp);
}
$ cc fstab.c
$ ./a.out
*** cannot open file
The program fails because SCO OpenServer 6 uses /etc/vfstab not /etc/fstab, but the error message does not indicate the file it failed to open. Running the program through truss displays each system call, along with its arguments and the return code:
$ truss ./a.out
execve("./a.out", 0x08047570, 0x08047578) argc = 1
open("/etc/fstab", O_RDONLY, 0666) Err#2
ENOENT
*** cannot open file
write(2, " * * * c a n n o t o".., 21) = 21
_exit(-1)
In a large program (especially one you are not that familiar with), truss can help you determine the point of failure. Using the -f option, truss can follow child processes as well as the parent process. You can also use truss to grab and release existing processes on the system.
Along with knowing symbolic names for many manifest constants (the O_RDONLY above, for example), truss can display additional information to further focus the trace. An explicit list of system calls to trace (or not to trace) can be given to truss with the -t option.
The -a and -e options respectively cause truss to display all the passed arguments and the entire environment at an exec.
Another advantage is that truss knows structures that cross the system call boundary. Using the -v option, you can specify system calls for which you want to see complete structures displayed.
Finally, all of the data read and/or written on a file descriptor (instead of the default first few bytes) can be displayed using the -r and/or -w options.
$ truss -w2 ./a.out
execve("./a.out", 0x08047570, 0x08047578) argc = 1
open("/etc/fstab", O_RDONLY, 0666) Err#2
ENOENT
*** cannot open file
write(2, 0x08047C14, 21) = 21
* * * c a n n o t o p e n f i l e\n
_exit(-1)
Note that you must be root to run truss on privileged programs.
A feature similar to truss is provided for dynamic executables by the runtime linker in SCO OpenServer 6.
When a dynamic executable calls a function that (potentially) is defined elsewhere -- outside of the calling shared library or executable -- a call is actually made to a bit of code in the "procedure linkage table" (PLT). This code then does an indirect branch to the target function. But, at process startup (by default), all the PLT entries end up branching to the runtime linker, which searches the executable and the current set of shared libraries for the actual definition of the target function. Once found, the runtime linker modifies the destination of the indirect branch for that PLT entry so that it jumps to the actual function.
Dynamic library tracing takes advantage of this lookup process and interposes a reporting function between the caller and the callee. It can also insert a bit of code to return to that comes between the callee and the caller that similarly reports the return value of the function.
As with truss, dynamic library tracing is disabled for processes that gain privilege for obvious security reasons.
This tracing differs from truss in a number of ways:
Dynamic library tracing is enabled by running a process with certain environment variables set:
Tracing the same fstab.c example from above:
$ LD_TRACE=sym
./a.out
atexit(0x80483b4) from 0x8048459
atexit(0xbff9b09c) from 0x8048465
atexit(0x8048514) from 0x804846f
__fpstart() from 0x804847b
fopen("/etc/fstab","r") from 0x80484b1
_findiop() from _realfopen+7
_open("/etc/fstab",0x0,0x1b6) from endopen+141
__thr_errno() from _cerror+29
fprintf(0x80495ec,"*** cannot open
file"...,0x0,0x8047cb8,0x8048485) from 0x80484cc
_findbuf(0x80495ec) from fprintf+116
_xflsbuf(0x8047c44) from _idoprnt+358
_write(2,0x8047c04,21) from _xflsbuf+89
*** cannot open file
fclose(0x0) from 0x80484d7
exit(-1) from 0x804848e
_exithandle() from exit+18
_setjmp(0xbfffe628) from _exithandle+100
_cleanup() from _exithandle+151
fflush(0x0) from _cleanup+9
Note that even with symbols enabled, the reporting of some addresses (like the caller of fopen) is still just a hexadecimal value. This is because the only names available to the reporting code are those exported from the executable and shared libraries and by default only the necessary few global names are exported by the linker in the executable. If you use pass the -Wl,-Bexport option to the linker (see above regarding exported names) all global functions are exported.
$ LD_TRACE=sym,ret ./a.out
atexit(0x80483b4) from 0x8048459
=> atexit returned 0
atexit(0xbff9b09c) from 0x8048465
=> atexit returned 0
atexit(0x8048514) from 0x804846f
=> atexit returned 0
__fpstart() from 0x804847b
=> __fpstart returned
fopen("/etc/fstab","r") from 0x80484b1
_findiop() from _realfopen+7
=> _findiop returned 0x80496d0
_open("/etc/fstab",0x0,0x1b6) from endopen+141
__thr_errno() from _cerror+29
=> __thr_errno returned 0xbfffedd0
=> _open returned -1
=> fopen returned 0x0
fprintf(0x80495ec,"*** cannot open
file"...,0x0,0x8047cb8,0x8048485) from 0x80484cc
_findbuf(0x80495ec) from fprintf+116
=> _findbuf returned 0x80495ec
_xflsbuf(0x8047c44) from _idoprnt+358
_write(2,0x8047c04,21) from _xflsbuf+89
*** cannot open file
=> _write returned 21
=> _xflsbuf returned 0
=> fprintf returned 21
fclose(0x0) from 0x80484d7
=> fclose returned -1
exit(-1) from 0x804848e
_exithandle() from exit+18
_setjmp(0xbfffe628) from _exithandle+100
_cleanup() from _exithandle+151
fflush(0x0) from _cleanup+9
=> fflush returned 0
=> _cleanup returned
=> _exithandle returned
Note in this example that the return from setjmp is not displayed. Certain special functions like setjmp are sensitive to their return address. Finally, a stack trace for write results in the following:
$ LD_TRACE=sym LD_TRACE_STACK=_write ./a.out [0] _write(2,0x8047bec,21) [1] _xflsbuf+89(0x8047c2c) [0xbffa7b05] [2] _idoprnt+358(0x8047c2c,0x80484f8,0x8047c88) [0xbffa1eb6] [3] fprintf+201(0x80495ec,"*** cannot open file"...,0x0,0x8047ca0,0x8048485) [0xbffa8195] [4] 0x80484cc(0x1,0x8047cac,0x8047cb4) Page 41 SCO OpenServer 6 [5] 0x80484cc(0x8047cac,0x8047cb4,0x0) *** cannot open file
C (and to some degree C++) programs are infamous for memory allocation/corruption problems that are difficult to debug. These can sometimes show up during ports of "working" code because a malloc/free mistake might be missed on one platform but not on another. Such problems can go undetected for some time. Commercial tools such as Purify can help track down such problems.
The Dev Sys has a similar tool created by SCO, called memtool. Consider this simple error-ridden program:
1 #includes <stdlib.h>
2 #includes <stdio.h>
3 #includes <string.h>
4
5 int main() {
6 char *p = malloc(7), *q = "yes, too long\n";
7 strcpy(p, q);
8 free(p);
9 q = 0;
10 *p = `Y';
11 realloc(p, 3);
12 fputs(p, stderr);
13 return *q;
14 }
Most of the time, this program will run without problems, but will occassionally fail with memory allocation errors. memtool can be used to pinpoint the code in your application that may cause memory-allocation-related problems. If you take the same program as above and recompile it with -g for maximum symbolic information, you can then run the executable using memtool. This produces the following diagnostic output:
$ cc -g error.c
$ /usr/ccs/bin/memtool ./a.out
==============================================================================
Some abuses or potential misuses of the dynamic memory allocation subsystem
have been detected. The following multiline diagnostics are descriptions
of what exactly has been seen. They may includes use of terms and concepts
with which you may be unfamiliar. Use the "-v" option to get the complete
version of this introduction.
==============================================================================
A block's spare bytes have been modified. This usually occurs due to
writing beyond the block's regular bytes, either because an insufficient
number of bytes were requested when the block was allocated or simply
due to a programming logic error.
History for block at address 0x8049b70:
*Stack trace when detected:
[0] free(ptr=0x8049b70)
[1] main(0x1,0x8047e24,0x8047e2c) [error.c@8] in ./a.out
[2] _start() [0x80485f4] in ./a.out
*Stack trace when block at 0x8049b70 was allocated:
[0] malloc(sz=7)
[1] main() [error.c@6] in ./a.out
[2] _start(presumed:0x1,0x8047e24,0x8047e2c) [0x80485f9] in ./a.out
Annotated snapshot surrounding the live allocation at address 0x8049b70
when the 5 bytes [0x8049b77,0x8049b7b] were found to have been modified.
This allocation holds 7 byte(s) followed by 5 extra (or spare) byte(s),
and, in this case, spare bytes were found to have been modified.
0x8049b60: 0x00000000 0x00000000 0x00000000 0x00000011 ................
: ******** ****
0x8049b70: 0x2c736579 0x6f6f7420 0x6e6f6c20 0x00000a67 yes, too long...
: -------- ^^------ ^^^^^^^^ -------^^^^^
==============================================================================
A block's header has been modified. Often this occurs due to mistakenly
writing past the end of the preceding block. You might also try using the
"-x" option to add spare bytes to the end of each block, and see whether
your application behaves differently.
History for block at address 0x8049b80:
*Stack trace when detected:
[0] free(ptr=0x8049b70)
[1] main(0x1,0x8047e24,0x8047e2c) [error.c@8] in ./a.out
[2] _start() [0x80485f4] in ./a.out
Annotated snapshot surrounding the live allocation at address 0x8049b80
when the 4 bytes [0x8049b7c,0x8049b7f] were found to have been modified.
This allocation holds 4 bytes, but, in this case, the allocation's
header was found to have been modified.
0x8049b70: 0x2c736579 0xca6f7420 0xcacacaca 0x00000a67 yes, to.....g...
: ^^^^^^^^ ^^^^
0x8049b80: 0x00000000 0x00000015 0xcacacaca 0xcacacaca ................
: -------- ----
==============================================================================
A recently free()d block was passed as the first argument to realloc().
Only null pointers or live block addresses are permitted to be passed to
realloc(), although, in this implementation, were dynamic memory checking
not enabled, this block's contents would have been preserved between its
being freed and this call to realloc(), but this is a nonportable feature
of this implementation which should not be relied on.
History for block at address 0x8049b70:
*Stack trace when detected:
[0] realloc(ptr=0x8049b70,sz=3)
[1] main(0x1,0x8047e24,0x8047e2c) [error.c@11] in ./a.out
[2] _start() [0x80485f4] in ./a.out
*Stack trace when block at 0x8049b70 was released:
[0] free(ptr=0x8049b70)
[1] main() [error.c@8] in ./a.out
[2] _start(presumed:0x1,0x8047e24,0x8047e2c) [0x80485f9] in ./a.out
*Stack trace when block at 0x8049b70 was allocated:
[0] malloc(sz=7)
[1] main() [error.c@6] in ./a.out
[2] _start(presumed:0x1,0x8047e24,0x8047e2c) [0x80485f9] in ./a.out
==============================================================================
A free()d block has been modified. This usually means that the block was
passed to free() or realloc(), but the block continued to be used by your
application, possibly far removed from the deallocating code.
History for block at address 0x8049b70:
*Stack trace when detected:
[0] realloc(ptr=0x8049b70,sz=3)
[1] main(0x1,0x8047e24,0x8047e2c) [error.c@11] in ./a.out
[2] _start() [0x80485f4] in ./a.out
*Stack trace when block at 0x8049b70 was released:
[0] free(ptr=0x8049b70)
[1] main() [error.c@8] in ./a.out
[2] _start(presumed:0x1,0x8047e24,0x8047e2c) [0x80485f9] in ./a.out
*Stack trace when block at 0x8049b70 was allocated:
[0] malloc(sz=7)
[1] main() [error.c@6] in ./a.out
[2] _start(presumed:0x1,0x8047e24,0x8047e2c) [0x80485f9] in ./a.out
Annotated snapshot surrounding the free allocation at address 0x8049b70
when the byte at 0x8049b70 was found to have been modified. This
allocation holds 12 bytes, one of which was found to have been modified.
0x8049b60: 0x00000000 0x00000000 0x00000000 0x00000011 ................
: ******** ****
0x8049b70: 0xcacaca59 0xcacacaca 0xcacacaca 0x00000179 Y...........y...
: ------^^ -------- -------- ^-----------
==============================================================================
LIVE ALLOCATIONS AT PROCESS EXIT
==============================================================================
Memory allocations that have not been released by the time your process is
finished are in no way an error, but they are potentially ``leaks'' --
allocations that inadvertently have not been released once they were no
longer needed or in use. If your application has more than a few live
allocations displayed below with the same size and/or were allocated at the
same location, you may well have a leak; or if your application, when run
with more data or for longer periods, displays more live allocations here,
you also may have a leak. A leak also need not be repaired: A short-lived
process can easily survive having many leaks, as long as they are not too
large and there are not so many that available memory resources could become
exhausted (or even strained). Moreover, it may well be that ``the leaks''
are allocations that were all in use up to just before the process exits,
and the effort and the expense to release them all is not warranted.
Stack trace for 4 byte block at 0x8049cf8:
[0] realloc(ptr=0x8049b70,sz=3)
[1] main() [error.c@11] in ./a.out
[2] _start(presumed:0x1,0x8047e24,0x8047e2c) [0x80485f9] in ./a.out
*Stack trace for block previously at 0x8049b70 before realloc() to 0x8049cf8:
[0] free(ptr=0x8049b70)
[1] main() [error.c@8] in ./a.out
[2] _start(presumed:0x1,0x8047e24,0x8047e2c) [0x80485f9] in ./a.out
The Dev Sys debugger, debug, is a powerful tool for source and assembly-level debugging. It supports both C and C++ language debugging. (For Java debugging, use the command-line jdb debugger.) The debugger has two basic user interfaces: command line and graphical.
Debugging the port of an existing application can be different from program development. Some debugger features that may be useful in this regard are:
The debug tool is not related to the dbx and gdb debuggers many Release 5 users are familiar with, and uses a different command syntax. However, there is "A Guide to debug for dbx Users" on page 63 that will help such users gain familiarity with debug.
For more information on the Dev Sys debugger, see the Dev Sys debug documentation. .