I'm building a CP/M based on the RC2014 pageable ROM and 64kB RAM modules, together with the IDE drive. The goal is to allow CP/M to load from ROM but using FATFS formatted drives.
The process is to have the CCP/BDOS and the BIOS loaded into the correct places out of ROM using a preamble and then boot into a monitor also in ROM. The user can then configure the FATFS files that are required to be the CP/M drives. Then the ROM is paged out, a new Page 0 is created, and then CCP/BDOS is started.
The issue is that somehow the CCP PHASE is being created with the first byte incorrect. The first byte of the CCP PHASE is being written as a $00 byte.
EDIT: It is an assembler issue. further notes below. The incorrectly overwritten byte seems to decrement by one for each byte prepended before the start of the PHASE. i.e. if 8 bytes are prepended, then the first byte is overwritten as $F8 after 7 correct prepends. If two bytes are prepended before the PHASE, then the first byte is overwritten as $FE following one correct prepend..
Listing of CCP below
1 0000 MODULE cpm22_asm
2 0000 LINE -1, "cpm22.asm"
-1 0000
0 0000
1 0000 ;
2 0000 ;
3 0000 ;**************************************************************
4 0000 ;*
5 0000 ;* C P / M version 2 . 2
6 0000 ;*
7 0000 ;* Reconstructed from memory image on February 27, 1981
8 0000 ;*
9 0000 ;* by Clark A. Calkins
10 0000 ;*
11 0000 ;**************************************************************
12 0000 ;
13 0000 ;
14 0000 ; Converted to z88dk z80asm for RC2014 by
15 0000 ; Phillip Stevens @feilipu https://feilipu.me
16 0000 ; March 2018
17 0000 ;
18 0000
19 0000 SECTION rodata_user ;read only user
20 0000
21 0000 ;------------------------------------------------------------------------------
22 0000 ; location setting
23 0000 ;------------------------------------------------------------------------------
24 0000
25 0000 PUBLIC __COMMON_AREA_PHASE_CCP_BDOS ;base of ccp
26 0000 defc __COMMON_AREA_PHASE_CCP_BDOS = 0xD800
27 0000
28 0000 ;------------------------------------------------------------------------------
29 0000 ; start of definitions
30 0000 ;------------------------------------------------------------------------------
31 0000
32 0000 EXTERN _cpm_iobyte
33 0000 EXTERN _cpm_cdisk
34 0000 EXTERN _cpm_ccp_tfcb
35 0000 EXTERN _cpm_ccp_tbuff
36 0000 EXTERN _cpm_ccp_tbase
37 0000
38 0000 DEFC IOBYTE = _cpm_iobyte ;i/o definition byte
39 0000 DEFC TDRIVE = _cpm_cdisk ;current drive name and user number
40 0000 DEFC ENTRY = _cpm_bdos_head ;entry point for the cp/m bdos
41 0000 DEFC TFCB = _cpm_ccp_tfcb ;default file control block
42 0000 DEFC TBUFF = _cpm_ccp_tbuff ;i/o buffer and command line storage
43 0000 DEFC TBASE = _cpm_ccp_tbase ;transient program storage area
44 0000
45 0000 ;
46 0000 ; Set control character equates.
47 0000 ;
48 0000 DEFC CNTRLC = 03H ;control-c
49 0000 DEFC CNTRLE = 05H ;control-e
50 0000 DEFC BS = 08H ;backspace
51 0000 DEFC TAB = 09H ;tab
52 0000 DEFC LF = 0AH ;line feed
53 0000 DEFC FF = 0CH ;form feed
54 0000 DEFC CR = 0DH ;carriage return
55 0000 DEFC CNTRLP = 10H ;control-p
56 0000 DEFC CNTRLR = 12H ;control-r
57 0000 DEFC CNTRLS = 13H ;control-s
58 0000 DEFC CNTRLU = 15H ;control-u
59 0000 DEFC CNTRLX = 18H ;control-x
60 0000 DEFC CNTRLZ = 1AH ;control-z (end-of-file mark)
61 0000 DEFC DEL = 7FH ;rubout
62 0000
63 0000 ;
64 0000 ; Set origin for CP/M
65 0000 ;
66 0000 PUBLIC _rodata_cpm_ccp_head
67 0000 _rodata_cpm_ccp_head: ;origin of the cpm ccp in rodata
68 0000
69 0000 PHASE __COMMON_AREA_PHASE_CCP_BDOS
70 D800
71 D800 PUBLIC _cpm_ccp_head
72 D800 _cpm_ccp_head: ;origin of the cpm ccp
73 D800
74 D800 CBASE:
75 D800 C3 45 DB JP COMMAND ;execute command processor (ccp).
76 D803 C3 41 DB JP CLEARBUF ;entry to empty input buffer before starting ccp.
77 D806
78 D806 ;
79 D806 ; Standard cp/m ccp input buffer. Format is (max length),
80 D806 ; (actual length), (char #1), (char #2), (char #3), etc.
81 D806 ;
82 D806 INBUFF:
83 D806 7F DEFB 127 ;length of input buffer.
84 D807 00 DEFB 0 ;current length of contents.
85 D808 43 6F 70 79 72 69 67 68 74 20 31 39 37 39 20 28 63 29 20 62 79 20 44 69 67 69 74 61 6C 20 52 65
D828 73 65 61 72 63 68
DEFM "Copyright 1979 (c) by Digital Research"
86 D82E 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
D84E 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
D86E 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
DEFS 89,0
Excerpt of the map file
_code_preamble_head = $00AB ; addr, public, , cpm22preamble_asm, code_crt_init, cpm22preamble.asm:24
__COMMON_AREA_PHASE_CCP_BDOS = $D800 ; const, public, , cpm22_asm, rodata_user, cpm22.asm:26
_rodata_cpm_ccp_head = $4E7C ; addr, public, , cpm22_asm, rodata_user, cpm22.asm:67
_cpm_ccp_head = $D800 ; const, public, , cpm22_asm, rodata_user, cpm22.asm:72
_cpm_ccp_tail = $DF98 ; const, public, , cpm22_asm, rodata_user, cpm22.asm:1397
_cpm_ccp_data_head = $DF98 ; const, public, , cpm22_asm, rodata_user, cpm22.asm:1401
_cpm_ccp_data_tail = $DFF0 ; const, public, , cpm22_asm, rodata_user, cpm22.asm:1436
_cpm_bdos_head = $E000 ; const, public, , cpm22_asm, rodata_user, cpm22.asm:1449
_cpm_bdos_tail = $ED60 ; const, public, , cpm22_asm, rodata_user, cpm22.asm:4069
_cpm_bdos_data = $ED60 ; const, public, , cpm22_asm, rodata_user, cpm22.asm:4080
_cpm_bdos_data_tail = $ED63 ; const, public, , cpm22_asm, rodata_user, cpm22.asm:4086
_cpm_bdos_bss_bridge = $ED63 ; const, public, , cpm22_asm, rodata_user, cpm22.asm:4089
_cpm_bdos_bss_head = $ED63 ; const, public, , cpm22_asm, bss_user, cpm22.asm:4102
_cpm_bdos_bss_tail = $EDEB ; const, public, , cpm22_asm, bss_user, cpm22.asm:4177
__COMMON_AREA_PHASE_BIOS = $EE00 ; const, public, , cpm22bios_asm, rodata_user, cpm22bios.asm:17
Further excerpt of the map file
phillip@SilverQuad:~/Z80/RC2014/ROMs/CPM-IDE$ grep \$4E7 rc2014-cpm22.map
___str_1 = $4E72 ; addr, local, , ff_c, rodata_compiler, ./ff.c:2972
___str_2 = $4E76 ; addr, local, , ff_c, rodata_compiler, ./ff.c:2976
_rodata_cpm_ccp_head = $4E7C ; addr, public, , cpm22_asm, rodata_user, cpm22.asm:67
__rodata_compiler_tail = $4E7C ; const, public, def, , ,
__rodata_user_head = $4E7C ; const, public, def, , ,
Compile line
phillip@SilverQuad:~/Z80/RC2014/ROMs/CPM-IDE$ zcc +rc2014 -SO3 --opt-code-size -subtype=acia -m --list -llib/rc2014/ff --c-code-in-asm --max-allocs-per-node20000 @cpm22.lst -o rc2014-cpm22 -create-app
The attached screenshot shows the $D800 origin of the CCP as $00. Also, there is a $00 byte at the _rodata_cpm_ccp_head, which indicates the linkage for the HEX file is being prepared incorrectly.

Also using the ff library to support FATFS on IDE.
Yes something looks wrong there. I'll try a build tonight. It would be interesting to see if the overwritten byte is also present in the binary or if it's just the hex file that is affected.
Yes. It is also there in the binary.
I moved the section to rodata_driver, which puts my driver in a different order. Just to experiment.
The first byte is $492E. It is indeed wrong in the binary and HEX again.
1 0000 MODULE cpm22_asm
2 0000 LINE -1, "cpm22.asm"
-1 0000
0 0000
1 0000 ;
2 0000 ;
3 0000 ;**************************************************************
4 0000 ;*
5 0000 ;* C P / M version 2 . 2
6 0000 ;*
7 0000 ;* Reconstructed from memory image on February 27, 1981
8 0000 ;*
9 0000 ;* by Clark A. Calkins
10 0000 ;*
11 0000 ;**************************************************************
12 0000 ;
13 0000 ;
14 0000 ; Converted to z88dk z80asm for RC2014 by
15 0000 ; Phillip Stevens @feilipu https://feilipu.me
16 0000 ; March 2018
17 0000 ;
18 0000
19 0000 SECTION rodata_driver ;read only driver (code)
20 0000
21 0000 ;------------------------------------------------------------------------------
22 0000 ; location setting
23 0000 ;------------------------------------------------------------------------------
24 0000
25 0000 PUBLIC __COMMON_AREA_PHASE_CCP_BDOS ;base of ccp
26 0000 defc __COMMON_AREA_PHASE_CCP_BDOS = 0xD800
27 0000
28 0000 ;------------------------------------------------------------------------------
29 0000 ; start of definitions
30 0000 ;------------------------------------------------------------------------------
31 0000
32 0000 EXTERN _cpm_iobyte
33 0000 EXTERN _cpm_cdisk
34 0000 EXTERN _cpm_ccp_tfcb
35 0000 EXTERN _cpm_ccp_tbuff
36 0000 EXTERN _cpm_ccp_tbase
37 0000
38 0000 DEFC IOBYTE = _cpm_iobyte ;i/o definition byte
39 0000 DEFC TDRIVE = _cpm_cdisk ;current drive name and user number
40 0000 DEFC ENTRY = _cpm_bdos_head ;entry point for the cp/m bdos
41 0000 DEFC TFCB = _cpm_ccp_tfcb ;default file control block
42 0000 DEFC TBUFF = _cpm_ccp_tbuff ;i/o buffer and command line storage
43 0000 DEFC TBASE = _cpm_ccp_tbase ;transient program storage area
44 0000
45 0000 ;
46 0000 ; Set control character equates.
47 0000 ;
48 0000 DEFC CNTRLC = 03H ;control-c
49 0000 DEFC CNTRLE = 05H ;control-e
50 0000 DEFC BS = 08H ;backspace
51 0000 DEFC TAB = 09H ;tab
52 0000 DEFC LF = 0AH ;line feed
53 0000 DEFC FF = 0CH ;form feed
54 0000 DEFC CR = 0DH ;carriage return
55 0000 DEFC CNTRLP = 10H ;control-p
56 0000 DEFC CNTRLR = 12H ;control-r
57 0000 DEFC CNTRLS = 13H ;control-s
58 0000 DEFC CNTRLU = 15H ;control-u
59 0000 DEFC CNTRLX = 18H ;control-x
60 0000 DEFC CNTRLZ = 1AH ;control-z (end-of-file mark)
61 0000 DEFC DEL = 7FH ;rubout
62 0000
63 0000 ;
64 0000 ; Set origin for CP/M
65 0000 ;
66 0000 PUBLIC _rodata_cpm_ccp_head
67 0000 _rodata_cpm_ccp_head: ;origin of the cpm ccp in rodata
68 0000
69 0000 PHASE __COMMON_AREA_PHASE_CCP_BDOS
70 D800
71 D800 PUBLIC _cpm_ccp_head
72 D800 _cpm_ccp_head: ;origin of the cpm ccp
73 D800
74 D800 CBASE:
75 D800 C3 45 DB JP COMMAND ;execute command processor (ccp).
76 D803 C3 41 DB JP CLEARBUF ;entry to empty input buffer before starting ccp.
77 D806
78 D806 ;
79 D806 ; Standard cp/m ccp input buffer. Format is (max length),
80 D806 ; (actual length), (char #1), (char #2), (char #3), etc.
81 D806 ;
82 D806 INBUFF:
83 D806 7F DEFB 127 ;length of input buffer.
84 D807 00 DEFB 0 ;current length of contents.
85 D808 43 6F 70 79 72 69 67 68 74 20 31 39 37 39 20 28 63 29 20 62 79 20 44 69 67 69 74 61 6C 20 52 65
D828 73 65 61 72 63 68
DEFM "Copyright 1979 (c) by Digital Research"
86 D82E 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
D84E 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
D86E 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
DEFS 89,0
phillip@SilverQuad:~/Z80/RC2014/ROMs/CPM-IDE$ grep \$492 rc2014-cpm22.map
l_f_getfree_00131 = $4926 ; addr, local, , ff_c, code_compiler, ./ff.c:6202
_rodata_cpm_ccp_head = $492E ; addr, public, , cpm22_asm, rodata_driver, cpm22.asm:67
__code_compiler_tail = $492E ; const, public, def, , ,
__code_user_head = $492E ; const, public, def, , ,
__code_user_tail = $492E ; const, public, def, , ,
__rodata_driver_head = $492E ; const, public, def, , ,


What is the compile line you are using?
phillip@SilverQuad:~/Z80/RC2014/ROMs/CPM-IDE/$ zcc +rc2014 -SO3 --opt-code-size -subtype=acia -m --list -llib/rc2014/ff --c-code-in-asm --max-allocs-per-node20000 @cpm22.lst -o rc2014-cpm22 -create-app
Also, I have tweaked the ff build to include some additional functions needed to get file and path info.
A new z88dk-lib +rc2014 -f ff is required to install this build.
Unrelated (probably).
The ALIGN keyword doesn't work in either the second PHASE or second SECTION in a file. Not sure which, so I haven't raised an issue until I'm clear about the cause, and so I have just avoided it twice.
If it helps, I reassembled with the option -Cl--split-bin, and the binary rc2014-cpm22_rodata_driver.bin is incorrect, with $00 as the first byte. So the listing of cpm22.asm.lis is correct, but the binary is wrong. It is not the linker at fault.

It gets more interesting...
19 0000 SECTION rodata_driver ;read only driver (code)
20 0000
21 0000
22 0000 55 00 testing: defw $55
23 0002
24 0002 ;------------------------------------------------------------------------------
25 0002 ; location setting
26 0002 ;------------------------------------------------------------------------------
27 0002
28 0002 PUBLIC __COMMON_AREA_PHASE_CCP_BDOS ;base of ccp
29 0002 defc __COMMON_AREA_PHASE_CCP_BDOS = 0xD800
30 0002
31 0002 ;------------------------------------------------------------------------------
32 0002 ; start of definitions
33 0002 ;------------------------------------------------------------------------------
34 0002
66 0002 ;
67 0002 ; Set origin for CP/M
68 0002 ;
69 0002 PUBLIC _rodata_cpm_ccp_head
70 0002 _rodata_cpm_ccp_head: ;origin of the cpm ccp in rodata
71 0002
72 0002 PHASE __COMMON_AREA_PHASE_CCP_BDOS
73 D800
74 D800 PUBLIC _cpm_ccp_head
75 D800 _cpm_ccp_head: ;origin of the cpm ccp
76 D800
77 D800 CBASE:
78 D800 C3 45 DB JP COMMAND ;execute command processor (ccp).
79 D803 C3 41 DB JP CLEARBUF ;entry to empty input buffer before starting ccp.
Gets this result $55 $00 $FE $45 $DB instead of $55 $00 $C3 $45 $DB

and this.
18 0000
19 0000 SECTION rodata_driver ;read only driver (code)
20 0000
21 0000
22 0000 55 testing: defb $55
23 0001
24 0001 ;------------------------------------------------------------------------------
25 0001 ; location setting
26 0001 ;------------------------------------------------------------------------------
27 0001
28 0001 PUBLIC __COMMON_AREA_PHASE_CCP_BDOS ;base of ccp
29 0001 defc __COMMON_AREA_PHASE_CCP_BDOS = 0xD800
30 0001
31 0001 ;------------------------------------------------------------------------------
32 0001 ; start of definitions
33 0001 ;------------------------------------------------------------------------------
34 0001
66 0001 ;
67 0001 ; Set origin for CP/M
68 0001 ;
69 0001 PUBLIC _rodata_cpm_ccp_head
70 0001 _rodata_cpm_ccp_head: ;origin of the cpm ccp in rodata
71 0001
72 0001 PHASE __COMMON_AREA_PHASE_CCP_BDOS
73 D800
74 D800 PUBLIC _cpm_ccp_head
75 D800 _cpm_ccp_head: ;origin of the cpm ccp
76 D800
77 D800 CBASE:
78 D800 C3 45 DB JP COMMAND ;execute command processor (ccp).
79 D803 C3 41 DB JP CLEARBUF ;entry to empty input buffer before starting ccp.
80 D806
Gets this result $55 $FF $45 $DB instead of $55 $C3 $45 $DB

The ALIGN keyword doesn't work in either the second PHASE or second SECTION in a file. Not sure which, so I haven't raised an issue until I'm clear about the cause, and so I have just avoided it twice.
It looks like ALIGN is anchored to the current section and is (unexpectedly) unaffected by the PHASE. You would expect ALIGN to work within the current PHASE.
So the listing of cpm22.asm.lis is correct, but the binary is wrong. It is not the linker at fault.
It is a bug in the linker because it's outputting the incorrect raw binaries before appmake gets hold of it. It is odd that the .lis file is correct but that's also just raw assembled bytes before the linker sorts in sections and patches external symbols. Some patching error might be at work.
So, the linker generates the binary files, before appmake gets them?
Of course... otherwise the jump locations wouldn't be filled in (and they are). Doh.
Therefore shift the blame back to the linker... yes.
Will change the issue title back...
@pauloscustodio
This one's a bit odd. The error is in the object file before the linker patches extern symbols.
The problem file is cpm22.asm. The linker is overwriting the $c3 in the jump at label CBASE and the overwrite value is different if stuff is inserted before the label. So I thought it was a patching error but that's not the case because the error is already present in the object file.
Make object file:
z80asm -l cpm22.asm
Look at the the object file contents:
z80nm -a cpm22.o | more
At offset 0 the sequence of bytes is:
Code: 5475 bytes (section rodata_driver)
C $0000: 55 FF 45 DB C3 41 DB 7F 00 43 6F 70 79 72 69 67
The FF has overwritten the C3 opcode for jump.
Here is the corresponding lis file:
22 0000 55 testing: defb $55
23 0001
24 0001 ;------------------------------------------------------------------------------
25 0001 ; location setting
26 0001 ;------------------------------------------------------------------------------
27 0001
28 0001 PUBLIC __COMMON_AREA_PHASE_CCP_BDOS ;base of ccp
29 0001 defc __COMMON_AREA_PHASE_CCP_BDOS = 0xD800
30 0001
31 0001 ;------------------------------------------------------------------------------
32 0001 ; start of definitions
33 0001 ;------------------------------------------------------------------------------
34 0001
35 0001 EXTERN _cpm_iobyte
36 0001 EXTERN _cpm_cdisk
37 0001 EXTERN _cpm_ccp_tfcb
38 0001 EXTERN _cpm_ccp_tbuff
39 0001 EXTERN _cpm_ccp_tbase
40 0001
41 0001 DEFC IOBYTE = _cpm_iobyte ;i/o definition byte
42 0001 DEFC TDRIVE = _cpm_cdisk ;current drive name and user number
43 0001 DEFC ENTRY = _cpm_bdos_head ;entry point for the cp/m bdos
44 0001 DEFC TFCB = _cpm_ccp_tfcb ;default file control block
45 0001 DEFC TBUFF = _cpm_ccp_tbuff ;i/o buffer and command line storage
46 0001 DEFC TBASE = _cpm_ccp_tbase ;transient program storage area
47 0001
48 0001 ;
49 0001 ; Set control character equates.
50 0001 ;
51 0001 DEFC CNTRLC = 03H ;control-c
52 0001 DEFC CNTRLE = 05H ;control-e
53 0001 DEFC BS = 08H ;backspace
54 0001 DEFC TAB = 09H ;tab
55 0001 DEFC LF = 0AH ;line feed
56 0001 DEFC FF = 0CH ;form feed
57 0001 DEFC CR = 0DH ;carriage return
58 0001 DEFC CNTRLP = 10H ;control-p
59 0001 DEFC CNTRLR = 12H ;control-r
60 0001 DEFC CNTRLS = 13H ;control-s
61 0001 DEFC CNTRLU = 15H ;control-u
62 0001 DEFC CNTRLX = 18H ;control-x
63 0001 DEFC CNTRLZ = 1AH ;control-z (end-of-file mark)
64 0001 DEFC DEL = 7FH ;rubout
65 0001
66 0001 ;
67 0001 ; Set origin for CP/M
68 0001 ;
69 0001 PUBLIC _rodata_cpm_ccp_head
70 0001 _rodata_cpm_ccp_head: ;origin of the cpm ccp in rodata
71 0001
72 0001 PHASE __COMMON_AREA_PHASE_CCP_BDOS
73 D800
74 D800 PUBLIC _cpm_ccp_head
75 D800 _cpm_ccp_head: ;origin of the cpm ccp
76 D800
77 D800 CBASE:
78 D800 C3 45 DB JP COMMAND ;execute command processor (ccp).
79 D803 C3 41 DB JP CLEARBUF ;entry to empty input buffer before starting ccp.
You can see the first few bytes should be 55 C3 45 DB C3 41 DB
Therefore shift the blame back to the linker... yes.
Let's put it back on the assembler as it looks like the object file is wrong :)
It is odd that the cpm22bios.asm file doesn't do the same thing. It is essentially identical, with a jp instruction as the first byte. It has the same PHASE and SECTION construction, so it should have exactly the same error.
I didn't actually check yet, as the first jp doesn't get hit unless CP/M is running. The preamble code jumps straight to the boot: label. So I didn't check what was at 0xEE00...
EDIT. I just checked both files by separating them into different SECTIONs and adding a defs 0x08 0x76 statement before the PHASE.
The cpm22bios.asm file is correct, but the cpm22.asm file generates 7 0x76 bytes, and then one 0xF8 byte. Curious...

This one was hairy!
IF a DEFC constant (ENTRY in cpm22bios.asm) was defined equal to a label (_cpm_bdos_head)
AND the label (_cpm_bdos_head) was in a PHASED block, therefore a constant and not an address,
THEN the expression value (_cpm_bdos_head) was being patched to the object file at the ASMPC
where it was referred to (DEFC ENTRY = _cpm_bdos_head was at ASMPC = 1, i.e. after the $55)
The $FF was the low byte, the high byte was later overwritten in the object file by the JP COMMAND
argument.
Fixed.
Thanks for reporting!
Most helpful comment
This one was hairy!
IF a DEFC constant (ENTRY in cpm22bios.asm) was defined equal to a label (_cpm_bdos_head)
AND the label (_cpm_bdos_head) was in a PHASED block, therefore a constant and not an address,
THEN the expression value (_cpm_bdos_head) was being patched to the object file at the ASMPC
where it was referred to (DEFC ENTRY = _cpm_bdos_head was at ASMPC = 1, i.e. after the $55)
The $FF was the low byte, the high byte was later overwritten in the object file by the JP COMMAND
argument.
Fixed.
Thanks for reporting!