This page was mostly written for the initial DryOS porting effort circa 2008, and is now largely obsolete. DryOS cameras are now well supported in CHDK and the normal CHDK development process. The general reverse engineering information found here is still useful, but readers looking for porting information should refer to Adding support for a new camera and For Developers.

OS Switch[]

Since late 2007 Digic-based cameras are shipped using Canon's own Operating System named DryOS. It replaces VxWorks from Wind River which has been used before on Digic II and some Digic III cameras. DryOS had existed before and was in use in other Canon hardware, such as digital video cameras and high-end webcams. The reason for the switch were most probably royalties Canon had to pay for VxWorks.


As of now, these cameras use DryOS.

An indicator for DryOS is that the "Firm Update" menu entry appears if a file named *.fi2 is put on the Card instead *.fir (as this was the extension of VxWorks firmwares). DSLRs differ, they use *.fir regardless of OS. In the firmware dump there is a copyright string like

Copyright (C) 1997-2007 by CANON Inc.
DRYOS version 2.3, release #0031

and also the string gaonisoy somewhere at the beginning.

Reverse Engineering the Firmware[]

A decent disassembler is necessary to figure out what the firmware does. It is assumed that IDA Pro will be used. There is already an article on loading a powershot-firmware into IDA (Loading dump to IDA). The steps to examine the unknown firmware are basically the same.

  1. Load firmware into IDA using the correct memory offset.
  2. Disassemble code.
  3. Locate functions and name them properly.
  4. Figure out
    • how to call firmware-functions from a C program
    • what the camera does when it starts and what it initializes
    • how to hook own tasks into the system
  5. Write a test program to check the discoveries using the bootdisk-method.
  6. Figure out how to enable the "Firm Update"-Menu entry (which does exist) to get an alternative to the bootdisk-method.

Load into IDA and Disassemble code[]

First I recommend reading Loading dump to IDA but don't follow the description just yet since some things are different.

To start with a new firmware you do:

  1. select "new disassembly" selecting "binary/raw File"
  2. choose your dump, keep "analysis options" unchecked (defaults are ok).
  3. select processor "ARM"
  4. keep "start anlysis now" checked.
  5. create a ROM section (PowerShot A-series, S/SD/SX/G-Series start address is 0xFF810000) [intrinsic: is this a typo? On the S5 1.01b firmware it's 0xFF800000, my IDA doesn't like 0xFF810000] [Answer: The dump should be loaded to the address it was dumped from. For the reason why the IDA refuses to load whole dump read a little bit below.]
    • Rom Start address: 0xFFC00000
    • Rom size: 0x003FFFFF
      This is a little odd since the dump is actually one byte larger. But since 0xFFFFFFFF - which would be the last byte of the firmware - is a reserved address in IDA we can't use it and have to chop off the last byte from the dump.
    • Loading address: 0xFFC00000
    • Loading size: 0x003FFFFF
    • Press <OK> and discover the next odd thing. Although instructed to create a ROM Segment from 0xFFC00000 to 0xFFFFFFFE the segment's size is only one byte. Since the Dump is loaded completely we simply have to grow the ROM section.
      To do this select edit->Segments->Edit Segment and set the "End address" from 0xFFC00001 to 0xFFFFFFFE
  6. 'Click on 'Open signatures window' tool-button. Or press 'Shift+F5'. '[1]

  7. Now Apply the CHDK.idc. It will not do everything it did on a VxWorks-Firmware but it disassembles most of the code. [Update: There is CHDK.idc adapted for DryOS.] Note that after running CHDK.idc the entry-code which started at the very beginning of the Firmware will not be disassembled. To get this chunk, you need to unassign (key 'u') the string which is at offset +4 ("gaonisoyP") since the "P" is part of the next instruction and IDA won't disassemble assigned memory locations. After that you can set the cursor to the first byte of the firmware and press 'c' to create the code at this location.
  8. Fix missing code. IDA stops disassembling when it thinks a function has ended. Unfortunately it thinks wrong sometimes. One particular reason to end disassembling is a BL-command. BL is "Branch with Link". It jumps to a subroutine and returns execution afterwards. In theory it is possible that a called subroutine does not return, but in the PowerShot-Firmware this is 99% not the case. So we need to fix that behaviour. Save the following code as 'code_fix.idc' and run it in IDA. It will look for BLs preceding unknown sections and disassemble it.
#include <idc.idc>

static findCode_fix(sb, se)
  auto a, c, w, d, b;

  c = 0;
  for (a=sb; a<se; a=a+4) {
    b = Byte(a-1);	// byte with jump-opcode (0xEB)
    d = Byte(a-2);	// distance of the jump (relative to current pos), most significant byte

    // fix stopped MakeCode after "BL xxx"
    if ((b==0xEB) && isUnknown(GetFlags(a))) {    // previous command: BL ... (short jump)
      if ( (d > 0xf8) || (d < 0x06) ){  // make sure, distance is some reasonable value (i.e. not to large)
        Message("Jump (%x) found at %x\n", d, a);
        c = c+1;

  Message( "Code found %d times\n", c);

static main()
  auto sb, se, b;

  Message("*** START OF ANALYSIS ***\n");

  sb = MinEA();  se = MaxEA();

  Message("Searching for code...\n");

  findCode_fix(sb, se);
  Message("Please wait...\n");

  Message("*** END OF ANALYSIS ***\n");

What you get by now is far from perfect. A lot of things need adjustment. Typical things that need to be fixed are

  • Subroutines that are declared shorter than they are.
    You will get this one very often. A subroutine is recognized, gets a large comment saying === S U B R O U T I N E === and a few lines of code later there's another comment saying End of function ... although it is clearly not the end.
    To fix this skip forward in the code until the actual end of the subroutine. Remember the address right after the end of the function, put the cursor back into the function code, press <Alt>-p to edit the function and set the End address to the noted address.
    Alternately, place the cursor on the new last line of the function and press the (lower case) 'e' key. You will be asked if you really want to change the end address: type 'y' or press RET (ENTER on some keyboards). Correct lengths of subroutines are necessary to get proper xref-trees.
  • Code isn't disassembled at all.
    This happens when IDA stopped processing a function or didn't find a reference to a function at all. Just put the cursor on the first unknown byte and press either 'c' to create code that is meant to belong to another subroutine or press 'p' to create a new subroutine. If you're unsure try 'c' first. If the disassembled code looks like a subroutine, you can still press 'p'.
  • incorrect ASCII strings.
    It sometimes happens that the start of a string isn't recognized. This can be confusing since references to this string aren't resolved correctly. In that case put the cursor on the first character of the string and press 'a'. The complete string will be labeled and xref'd correctly.

Firmware Begin[]

String "gaonisoy" in Beginning of Firmware.

ROM:FFC00000                 B       EntryPoint
ROM:FFC00000 ; ---------------------------------------------------------------------------
ROM:FFC00004 aGaonisoy       DCB "gaonisoy"
ROM:FFC0000C ; ---------------------------------------------------------------------------
ROM:FFC0000C EntryPoint                              ; CODE XREF: RestartDevice:loc_FFC00000
ROM:FFC0000C                 LDR     R1, =0xC0410000
ROM:FFC00010                 MOV     R0, #0

Locate and name functions[]

Since most of the code is now disassembled we can now start to give it proper names or - if that's not possible - at least something more meaningful than sub_FFDF448C.

GrAnd has partial signatures and some idc's for processing DryOS dumps in IDA they can be found here or here.


The easiest thing to start with is the assert-function. Throughout the code there are things that may never fail. If the do something has gone so terribly wrong that a generic error-handler is called and the running function is aborted. That generic error-handler is Assert.

For debugging-purposes Assert writes the name of the source-file as well as the line-number somewhere. So whenever we see something like

CMP   R1, #0
ADREQ =aSourceFile    ; "SourceFile.c"
MOVEQ R1, #0x7f
BLEQ  Assert

We know (at least) that this code comes from a file named "SourceFile.c" and that the condition that jumps to the Assert-function is something fatal and we don't need to investigate that. We can also name that function to something somewhat meaningful. I used the prefix 'u' for 'unknown' or 'unsure' followed by the name of the sourcefile an (optionally) a number. So this function would be called "uSourceFile1". Whenever it is called from somewhere else, IDA will show its name and we know we've already seen it and will be given a clue what it might do.

To find a function, in that case "Assert" we use the Strings window. It should have been populated by IDA with the strings in the Dump. Click the Strings Windows and press <Alt>-t to search for a string. Enter "assert" and click "OK". The cursor jumps to the next occurrence. Press <Ctrl>-t to search for the next match. The exact string we are looking for is \nAssert: File %s Line %d\n.

If you found it, doubleclick the line in the Strings Window and the Disassembler view will scroll to the position in the firmware. If you found the right location you will see (addresses may differ):

ROM:FFC0C0C8 aAssertFileSLin DCB 0xA                 ; DATA XREF: sub_FFC0C098+14
ROM:FFC0C0C8                 DCB "Assert: File %s Line %d",0xA,0

The IDA-View now shows the position in the Firmware that contains that string. The comment tells us, where this string is referenced (DATA XREF: sub_FFC0C098+14). Doubleclick on "sub_..." to jump to the actual code. You will see (again, addresses may differ)

ROM:FFC0C098 sub_FFC0C098                            ; CODE XREF: sub_FFC05088+34j
ROM:FFC0C098                                         ; sub_FFC057B0+30p ...
ROM:FFC0C098                 LDR     R2, =0x1B08
ROM:FFC0C09C                 LDR     R2, [R2]
ROM:FFC0C0A0                 CMP     R2, #0
ROM:FFC0C0A4                 MOVEQ   R2, R1
ROM:FFC0C0A8                 MOVEQ   R1, R0
ROM:FFC0C0AC                 ADREQ   R0, aAssertFileSLin ; "\nAssert: File %s Line %d\n"
ROM:FFC0C0B0                 BEQ     sub_FFC00EE8
ROM:FFC0C0B4                 BXNE    R2
ROM:FFC0C0B4 ; End of function sub_FFC0C098

We can see that this function prepares the String "Assert: File <filename_here> Line <linenumber>" and jumps somewhere else. I tried to follow what it does, but it was to confusing and I was just satisfied with the amount of information the repeated assertations give me.

Since we found this function, we name it properly. Make sure the cursor is in the function code and press <Alt>-p. This opens the "Edit function" dialog. In the field "Name of function" replace "sub_FFC0C098" with "Assert" and click ok.

Every reference to this function will be named "Assert" and is therefore easily understandable. Note that in the "DryOS Canon Firmware; A720-based" signatures, this function is named DebugAssert.


Many functions are named and registered by a function called RegisterEventProcedure and a few other, partly derived functions.

It usually works like this:

  1. LDR R0, =FunctionAddress
  2. ADR R1, aFunctionName
  3. BL RegisterEventProcedure

By knowing the address of RegisterEventProcedure it's pretty easy to spot registration-functions that do nothing but registering a bunch of other functions.

First we look for RegisterEventProcedure. As with Assert we salvage debug-information to discover and name functions. In this case we are looking for the string "RegisterEventProcedure: INVALID_HANDLE". This is an error message the hunted function may return and a hint for us where to look.

Again, use the strings Window to search for "RegisterEventProcedure: INVALID_HANDLE". If you found it, navigate to the IDA-View with the string and after that to the function referring the string. You should see:

ROM:FFC0C13C sub_FFC0C13C                            ; CODE XREF: sub_FFC0C1D0+4�j
ROM:FFC0C13C                                         ; sub_FFC0C1D8+4�j ...
ROM:FFC0C13C var_18          = -0x18
ROM:FFC0C13C                 STMFD   SP!, {R3-R7,LR}
ROM:FFC0C140                 MOV     R7, R0
ROM:FFC0C144                 LDR     R4, =0x1B0C
ROM:FFC0C148                 MOV     R0, #0
ROM:FFC0C14C                 MOV     R6, R2
ROM:FFC0C150                 MOV     R5, R1
ROM:FFC0C154                 STR     R0, [SP,#0x18+var_18]
ROM:FFC0C158                 LDR     R0, [R4,#8]
ROM:FFC0C15C                 MOV     R1, R7
ROM:FFC0C160                 MOV     R2, SP
ROM:FFC0C164                 BL      sub_FFC1A2E4
ROM:FFC0C168                 CMP     R0, #7
ROM:FFC0C16C                 ADREQ   R1, aRegistereventp ; "RegisterEventProcedure: INVALID_HANDLE"
ROM:FFC0C170                 BEQ     loc_FFC0C198
ROM:FFC0C174                 CMP     R0, #0
ROM:FFC0C178                 LDREQ   R0, [SP,#0x18+var_18]
ROM:FFC0C17C                 BLEQ    sub_FFC1979C
ROM:FFC0C180                 MOV     R0, #8
ROM:FFC0C184                 BL      sub_FFC19798
ROM:FFC0C188                 CMP     R0, #0
ROM:FFC0C18C                 STR     R0, [SP,#0x18+var_18]
ROM:FFC0C190                 BNE     loc_FFC0C1B0
ROM:FFC0C194                 ADR     R1, aRegistereven_0 ; "RegisterEventProcedure: ALLOC_ERROR"

Now use the edit function dialog to change the name to RegisterEventProcedure. Right below RegisterEventProcedure there are two other functions which do nothing but setting R2 to 0 and branching to Reg.... Name them RegisterEventProcedure_im1 and ..._im2 since they will be called as well.

Even a bit further down there is the reverse. It writes the string "UnRegisterEvntProc: " and that's just its name. So call this one UnRegisterEvntProc.

The general Rule for error messages is "name_of_function: what went wrong". This rule is applied by the Canon developers and we can make heavy use of it.

One more function calls RegisterEventProcedure. It is called ExportToEventProcedure and used to register the libc-functions. It is registered along with a lot of other functions. Use the String Window to find ExportToEventProcedure. It is right after "ExecuteEventProcedure" and libc-function-names like "strcpy" and "sprintf".

When you found it, jump to the referencing code and you will see

ROM:FFDD80B4                 LDR     R1, =sub_FFC0CC20
ROM:FFDD80B8                 ADR     R0, aExporttoeven_0 ; "ExportToEventProcedure"
ROM:FFDD80BC                 BL      sub_FFC0CC20

This is somewhat funny. ExportToEventProcedure is called to register itself. The address assigned to R1 is the function address. The address assigned to R0 is the name of the function. Click on =sub_xxxxxxxx and press <Alt>-<Enter>. This opens another IDA-View with the referenced code. Change the function name to ExportToEventProcedure and close the new Window. All BLs to ExportEventProcedure should now use the correct name instead of the address.

Continue renaming all other functions that are registered here.

The same game goes for the "PT_"-Functions. Search for "PT_GetSystemTime" to get you started.

Also look for "SSAPI::" and "drysh". At the front of the "SSAPI::" functions, you'll find the name loaded into R0 and then a call to a set of common functions, the first being a "Log" function, a check for a HardwareError, and a check for LowBattery status. If you search for all calls to the "Log" function, you will be led to an enormous number of functions that you can now name.

When you did all these the Firmware should be a little easier to understand.

enable/disable IRQs[]

Here's the function code that enables/disables Interrupts. I accidentially ran into these. They are named nowhere and are hard to find without knowing what to look for.

ROM:FFC00578 ; =============== S U B R O U T I N E =======================================
ROM:FFC00578 ; Returns: R0: previous state of IRQ-Flag
ROM:FFC00578 ;              0x00 => irq enabled
ROM:FFC00578 ;              0x80 => irq disabled
ROM:FFC00578 IRQdisable                              ; CODE XREF: sub_FFC00A0C+70�p
ROM:FFC00578                                         ; sub_FFC00F24+8�p ...
ROM:FFC00578                 MRS     R1, CPSR
ROM:FFC0057C                 AND     R0, R1, #0x80
ROM:FFC00580                 ORR     R1, R1, #0x80
ROM:FFC00584                 MSR     CPSR_cxsf, R1
ROM:FFC00588                 RET
ROM:FFC00588 ; End of function IRQdisable
ROM:FFC0058C ; =============== S U B R O U T I N E =======================================
ROM:FFC0058C ; Input: R0: desired state of the IRQ-bit
ROM:FFC0058C ;            0x00 => IRQ enabled
ROM:FFC0058C ;            0x80 => IRQ disabled
ROM:FFC0058C IRQrestore                              ; CODE XREF: sub_FFC00A0C+8C�p
ROM:FFC0058C                                         ; sub_FFC00F24+28�p ...
ROM:FFC0058C                 MRS     R1, CPSR
ROM:FFC00590                 BIC     R1, R1, #0x80
ROM:FFC00594                 ORR     R1, R1, R0
ROM:FFC00598                 MSR     CPSR_cxsf, R1
ROM:FFC0059C                 RET
ROM:FFC0059C ; End of function IRQrestore
ROM:FFC005A0 ; =============== S U B R O U T I N E =======================================
ROM:FFC005A0 IRQenable                               ; CODE XREF: sub_FFC049B0+8�p
ROM:FFC005A0                                         ; sub_FFC049C8+8�p ...
ROM:FFC005A0                 MRS     R1, CPSR
ROM:FFC005A4                 BIC     R1, R1, #0x80
ROM:FFC005A8                 MSR     CPSR_cxsf, R1
ROM:FFC005AC                 RET
ROM:FFC005AC ; End of function IRQenable

Short ARM ASM Overview[]

This is a short overview over regular ARM commands as well as some compiler-generated nastiness. Full detail is available in the ARM-Reference-guides linked in the reference-section.

Common commands include

  • LDR Load memory into a register
  • STR write register-contents to memory
  • CMP/CMN/TST evaluate Expression and set Status-Flags accordingly
  • B Branch. Continues program execution at the given location
  • BL Branch with Link. Continues program execution at given location and stores current location in LR (Link Register).
  • BX Branch and change execution mode (between ARM and Thumb mode)

Nearly every command can evaluate the status flags. For that a condition-suffix is added to the command. The command only executes if the condition is met. E.g. B means "branch", BCS means "branch if Carry Set" The condition-suffixes are (copy-n-paste from the Technical Reference Manual):

  • EQ Equal
  • NE Not equal
  • CS Unsigned higher, or same
  • CC Unsigned lower
  • MI Negative
  • PL Positive, or zero
  • VS Overflow
  • VC No overflow
  • HI Unsigned higher
  • LS Unsigned lower, or same
  • GE Greater, or equal
  • LT Less than
  • GT Greater than
  • LE Less than, or equal
  • AL Always

Registers and other CPU-Specific things

  • R0 - R12 are general purpose 32bit wide registers. In CHDK, R12 is sometimes referred to as IP.
  • R13, a.k.a. SP is the Stack Pointer. Although I haven't seen it being referred to as R13 in IDA.
  • LR is the Link Register. It is set by the BL command
  • PC is the Program Counter. It holds the address of the currently executed command (this is not 100% accurate, see the manual for details).
  • CPSR is the Current Program Status Register. It holds the Status Flags and several internal Information.
  • SPSR is the Saved Program Status Register. I haven't seen this in the Firmware.

Specific code-constructs can be seen more than once. Here are some:

  • Entering and leaving subroutines.
    On executing BL the CPU continues execution at the location in the argument and stores the old location in the LR (Link Register). At the beginning of the subroutine you will often see a command like STMFD SP!,{R4-R7,LR}. This stores Registers R4, R5, R6, R7 as well as LR to the Stack and increases the Stack Pointer (SP). The particular Registers saved will differ depending on which will be used by the subroutine.
    At the end of the subroutine you will usually see something like LDMFD SP!,{R4-R7,PC}. This will take Values from the Stack and store them in R4-R7 as well as PC. Note that the value that was read from LR is stored to PC (the Program Counter) causing the execution to be continued in the calling function.
    Also note, that Registers other than R4-R7 will remain in the state in which the subroutine left them. Usually R0 and R1 are used to share values between caller and callee.
  • A modification is STMFD SP!,{R4-R7,LR} at the start and LDMFD SP!,{R4-R7,LR}; B <sub_2> at the end of a function. This subroutine restores the initial state including the LR and branches to another subroutine. When <sub_2> returns, it will directly return to the original caller.

Referencing memory. Some usual memory-referencing methods

  • LDR R0, R1 loads the value of R1 to R0
  • LDR R0, [R1] loads the value of the address referenced by R1 to R0
  • LDR R0, [R1, #1] same as above, referenced addr is R1 + 1
  • LDR R0, [R1], #1 same as above, increases R1 by 1 afterwards

To store an immediate value in a Register you use MOV

  • MOV R0, #0 writes 0 to R0

Meaning of multiple Registers as Arguments

  • ADD R0, R1, R2 general rule: R0 is the target, R1 and R2 are operands. In that case: Add R1 and R2, store the result in R0.
  • ADD R0, R0, R1 Adds R1 to R0