
The Apple Network Server's all-too-secret weapon (featuring PPC Toolbox)
An incredible history of the Apple Network Server (running AIX on PowerPC), along with network IPC via AppleTalk to a 68k.

An incredible history of the Apple Network Server (running AIX on PowerPC), along with network IPC via AppleTalk to a 68k.
The lscbx instruction is almost a perfect string copy instruction, XER takes a number of bytes to load up regs with at max, and a sentinel byte to stop at. This instruction appears to only be present on the 601. I wrote the following string copy algorithm for the 32 bit Linux ABI on the 601 to play around with it.
### char * my_strcpy(char *dest, char *src);##.globl my_strcpymy_strcpy:mflr 0stw 0, 4(1)# Preconditions Check:# dest, and src are non-nullcmpwi 3, 0beqlrcmpwi 4, 0beqlrstw 3, 8(1)# Save Dest pointer in Senders output argument area# Enter Stack Frame# | LR SAVE |# | backchain | 128# |-----------|# | r31 | 124# | ... | ...# | r14 | 56# | padding ..|# | ... |# | Param save| 28# | LR Save | 4# | backchain | 0# LR/Paramter Save Area reserved reserved at bottom 6 words# 18 words reserved at top for R14:R31 inclusive (24*4=96, quadword align to 128)stwu 1, -128(1)stmw 14, 56(1)# Setup for String Copy, R5:R31 108 bytes at a time# stopping at NUL characterli 5, 0x6c # this will stay 0x6c as long as loop runs with no matchmtxer 5LT..0:lscbx. 5, 0, 4 # 108 bytes or EOFstswx 5, 0, 3 # 108 byes or til EOF# Increment Read/Write pointersaddi 3, 3, 0x6c # we may over shoot on the tail, but unless matchaddi 4, 4, 0x6c # occured, it doesn't matter# Check termination conditionbne LT..0# done Restorelmw 14,56(1)addi 1, 1, 128# Set Return Pointerlwz 3, 8(1)lwz 0, 4(1)mtlr 0blr
This algorithm is quite possibly very slow. The stmw/lmw instructions arn't necessarily favored, shorts string won't be worth spilling all non-volatile registers to memory over. lscbx may be very slow. This isn't intended to be used.
Qemu post 6.2 actually dropped support for the 601, the only CPU that supports the intruction. Upon rebuilding qemu, observed that the lscbx instruction has the following generator from targets/ppc/translate.c around line 5700.
/* lscbx - lscbx. */
static void gen_lscbx(DisasContext *ctx)
{
TCGv t0 = tcg_temp_new();
TCGv_i32 t1 = tcg_const_i32(rD(ctx->opcode));
TCGv_i32 t2 = tcg_const_i32(rA(ctx->opcode));
TCGv_i32 t3 = tcg_const_i32(rB(ctx->opcode));
gen_addr_reg_index(ctx, t0);
gen_helper_lscbx(t0, cpu_env, t0, t1, t2, t3);
tcg_temp_free_i32(t1);
tcg_temp_free_i32(t2);
tcg_temp_free_i32(t3);
tcg_gen_andi_tl(cpu_xer, cpu_xer, ~0x7F);
tcg_gen_or_tl(cpu_xer, cpu_xer, t0);
if (unlikely(Rc(ctx->opcode) != 0)) {
gen_set_Rc0(ctx, t0);
}
tcg_temp_free(t0);
}
Notably, you can see that if Rc bit is set (IE. set flags), then CR0 is set by comparing the returned value of the instruction in t0 ( then number of bytes copied) with zero.
The AIX Assembly reference has the following description of the behavior:
If Rc = 1 and XER(25-31) = 0, then Condition Register Field 0 is
undefined. If Rc = 1 and XER(25-31) <> 0, then Condition Register Field 0 is set as follows:
LT, GT, EQ, SO = b'00'||match||XER(SO)
This is the behavior that DingusPCC implements in cpu/ppc/poweropcodes.cpp
void dppc_interpreter::power_lscbx(uint32_t opcode) {
if (rec) {
ppc_state.cr =
(ppc_state.cr & 0x0FFFFFFFUL) |
(is_match ? CRx_bit::CR_EQ : 0) |
((ppc_state.spr[SPR::XER] & XER::SO) >> 3);
}}
Qemu actually removed the code for the 601 anyway. But, I found this an intresting disgression. lscbx is pretty cool, its seems to be whats missing from the x86 string instructions, which can't copy until either a NUL OR a limit like the 601 could.
Why did qemu deprecate the 601? What emulators do you guys use?
I have been exploring AIX and I thought I would write up how to write a simple Hello World program in assembly using the AIX toolchain and debuggers.
Some background about the toolchain : AIX has an `as` assembler as apart of the base image, and an `ld` linker, these are not the GNU as/ld , and have different options. Additionally, The outputs are XCOFF binary format, not ELFs. The AIX debugger is ` dbx`. All of these have man pages where some basic usage is called out.
AIX runs on POWER architecture machines, which are a RISC machine. The property of importance here is that the instructions are fixed width (4 bytes), and the word size/address space is 8 bytes wide. Obviously, you can't fit an entire address in an instruction, and so AIX (and Linux PPC), use complicated relocation and register relative addressing to enable loads and stores. This is enabled by the TOC, which is customarily place in register `r2` . Each function can be associated with its own TOC (or a shared module TOC), and function descriptors (DS) are used to associate a function with a TOC.
Behold, the assembly (which you can easily generate simiiar examples with gcc as apart of the AIXToolbox).
.extern .kwrite #external symbols for syscalls defined
.extern ._exit
.set STDOUT_FILENO,1
.globl __start[DS]
.csect __start[DS],3 # function descriptor for __start entry point
__start:
.llong .__start, TOC[tc0], 0
.csect .text[PR],5
.globl .__start
.__start: # actual code for entrypoint
mflr 0 #prologue, move LR to register r0
std 0, 16(1) #save r0 (IE. the LR) in sender link area
stdu 1, -128(1) #allocate our stack frame (Required by ABI) because we are non-leaf
li 3, STDOUT_FILENO # r3 is used as argument1 for syscall
ld 4, hello_world_desc(2) # load address of string into r4 relative to r2 (TOC)
li 5, hello_world_len # r5 (argument 3) set to length
bl .kwrite # do syscall via call (see notes below)
nop # ABI required by for linker to fixup r2
xor 3, 3, 3 # zero exit code stored in argument r3 for exit syscall
bl ._exit # invoke exit syscall
nop
addi 1, 1, 128 # dead code for epilogue
ld 0, 16(1)
mtlr 0
blr # this would otherwise be a ret instruction
.csect .data[RW], 4
hello_world_str:
.byte "Hello, World"
.byte 10
.set hello_world_len, $ - hello_world_str
.toc
hello_world_desc: .tc hello_world_str[TC],hello_world_str
A Note on Syscalls on AIX:
AIX uses a convention somewhat analogous to Windows NT, in that applications ordinarily do not make syscalls directly, and the syscall number isn't an ABI feature (a la Linux). Windows NT exposes its sycall layer through ntdll.dll as functions. This shared library is mapped in all processes loaded, requested or not. Similarly, AIX maps the kernel (/unix) unasked, at address 0x0000000000000000 . The kernel exposes a set of symbols to processes that the loader is capable of binding in processes. The kernel text actually contains the functions which wrap syscalls (a la ntdll.dll), and userspace programs call these entrypoints which preform a syscall instruction to cross the protection boundary.
This raises a small difficulty linking, these symbols must be bound by linking against the kernel import file. This is contained as apart of the the `bos.adt` packages. Alternatively, what I did was create my own import file (./kern.imp)
#!/unix
.kwrite
._exit
This can be assembled as follows:
`as -o hello_world_kernel.o -a64 -mPWR9 hello_world_kernel.S`
Note that the assembler defaults to the 32 bit unless '-a64' is provided.
I used to following command to link
`ld -b64 -o hello_world_kernel hello_world_kernel.o -bI:./kern.imp`
We can now observe the binary. The traditional UNIX equivalent of strace is `truss`.
truss ./hello_world_kernel
execve("./hello_world_kernel", 0x2FF22C54, 0x20016878) argc: 1
kusla(6, 0x09FFFFFFF0001170) = 0
Hello, World
kwrite(1, " H e l l o , W o r l d".., 13) = 13
_exit(0)
Additionally we can trace up to the syscall in dbx.
$ dbx ./hello_world_kernel
Type 'help' for help.
reading symbolic information ...warning: no source compiled with -g
(dbx) stop in __start
[1] stop in __start
(dbx) map
Entry 1:
Object name: hello_world_kernel
Text origin: 0x100000000
Text length: 0x3d3
Data origin: 0x110000290
Data length: 0x40
File descriptor: 0x4
Entry 2:
Object name: /usr/ccs/bin/usla64
Text origin: 0x9fffffff0000000
Text length: 0xf82e
Data origin: 0x9fffffff000f82e
Data length: 0x0
File descriptor: 0x5
(dbx) cont
[1] stopped in __start at 0x100000200
0x100000200 (__start) 7c0802a6 mflr r0
(dbx) listi
0x100000200 (__start) 7c0802a6 mflr r0
0x100000204 (__start+0x4) f8010010 std r0,0x10(r1)
0x100000208 (__start+0x8) f821ff81 stdu r1,-128(r1)
0x10000020c (__start+0xc) 38600001 li r3,0x1
0x100000210 (__start+0x10) e8820000 ld r4,0x0(r2)
0x100000214 (__start+0x14) 38a0000d li r5,0xd
0x100000218 (__start+0x18) 48000025 bl 0x10000023c (kwrite)
0x10000021c (__start+0x1c) e8410028 ld r2,0x28(r1)
0x100000220 (__start+0x20) 7c631a78 xor r3,r3,r3
0x100000224 (__start+0x24) 48000041 bl 0x100000264 (_exit)
(dbx) stepi
stopped in __start at 0x100000204
0x100000204 (__start+0x4) f8010010 std r0,0x10(r1)
...
0x100000218 (__start+0x18) 48000025 bl 0x10000023c (kwrite)
(dbx) stepi
stopped in kwrite at 0x10000023c
0x10000023c (kwrite) e9820008 ld r12,0x8(r2)
(dbx) stepi
stopped in kwrite at 0x100000240
0x100000240 (kwrite+0x4) f8410028 std r2,0x28(r1)
(dbx) stepi
stopped in kwrite at 0x100000244
0x100000244 (kwrite+0x8) e80c0000 ld r0,0x0(r12)
(dbx) stepi
stopped in kwrite at 0x100000248
0x100000248 (kwrite+0xc) e84c0008 ld r2,0x8(r12)
(dbx) stepi
stopped in kwrite at 0x10000024c
0x10000024c (kwrite+0x10) 7c0903a6 mtctr r0
(dbx) stepi
stopped in kwrite at 0x100000250
0x100000250 (kwrite+0x14) 4e800420 bctr
(dbx) p $r0
0x0000000000003700
(dbx) listi 0x3700
0x0000000000003700 4cc63342 crorc cr6,cr6,cr6
0x0000000000003704 44000002 sc 0x0
...
(dbx) nexti
Hello, World
stopped in __start at 0x10000021c
0x10000021c (__start+0x1c) e8410028 ld r2,0x28(r1)
(dbx) stepi
stopped in __start at 0x100000220
0x100000220 (__start+0x20) 7c631a78 xor r3,r3,r3
(dbx) stepi
stopped in __start at 0x100000224
0x100000224 (__start+0x24) 48000041 bl 0x100000264 (_exit)
(dbx) stepi
execution completed
(dbx) q
I am continuing to explore AIX, so some of this analysis may be incorrect , or change later. But, its been fun so far.
Post RISC assembly or other AIX secrets.
Here is an interesting demo with a young Steve Jobs, where he walks over the NeXTSTEP 3 OS.
I am amazed at how polished and integrated the system is, even with the 3rd party products of the time (Novell Netware, AppleTALK), and intra-application. Features like 'services' exist in Android sharing, but generally still don't seem common in our computing environments.
In a strange juxtaposition, here is Metasploit running on modernish AIX (7.2), with CDE, the vintage UNIX desktop. AIX long ago abandoned the workstation market, but even today, you can still install CDE on AIX 7.3.
In a Youtube, video , the author of the blog from which the photos are taken, demos catching a callback metasploit shell from Windows 10.
Running MSF on metasploit was no easy task, and the details of how the author was able to edit the build system of metasploit dependencies to enable building are here. Blog Post with Details
I found this mixture of old and new, and the CDE aesthetic quite compelling.