The DWARF standard, in the area of stack unwinding, uses the term CFA quite a lot. It contains the following passage explaining what the CFA is:

The call frame is identified by an address on the stack. We refer to this address as the Canonical Frame Address or CFA. Typically, the CFA is defined to be the value of the stack pointer at the call site in the previous frame (which may be different from its value on entry to the current frame).

The word "typically" in the above leaves the definition of the CFA slighty up in the air. A subsequent example from the document seems to defer the definition to the platform's ABI committee: (emphasis mine)

The following example uses a hypothetical RISC machine in the style of the Motorola 88000.

...

  • There are 8 4-byte registers:
    • ...
    • R7 stack pointer.
  • The stack grows in the negative direction.
  • The architectural ABI committee specifies that the stack pointer (R7) is the same as the CFA.

We now move from the DWARF standard to the x86_64 ABI, which contains the following squirreled away in the function reference:

_Unwind_GetCFA

uint64 _Unwind_GetCFA(struct _Unwind_Context *context);

This function returns the 64-bit Canonical Frame Address which is defined as the value of %rsp at the call site in the previous frame.

With this, we can happily say that on x86_64, cfa == rsp (at the call site in the previous frame). This statement has a few consequences:

  1. The unwind rule for cfa specifies how to restore rsp. Combined with the stack growing downwards, this is why the cfa unwind rule is typically cfa = rsp + N or cfa = rbp + N.
  2. Unwind rules can be specified for rsp, but they have no effect - the implicit rule rsp = cfa is always used for restoring rsp.
  3. If you want to specify an exotic unwind rule for rsp, then you have to instead use an exotic rule to restore cfa. In turn, this might make it difficult to specify unwind rules for other registers (as said rules are typically specified relative to cfa) - one workaround is to use two call frames: one to restore rsp and one to restore everything else.

One observation related to the workaround in point 3 is that the unwind rule for rip doesn't have to tell the truth. The unwind rule for rip is typically rip = [cfa - 8] because this matches the x86_64 ret semantics of rsp += 8; rip = [rsp - 8], but even if your function returns via ret, you aren't obliged to specify rip = [cfa - 8] as the unwind rule for rip: you can specify whatever small white lie you need to get the correct unwind behaviour.