How is a two-pass assembler designed?
Different assemblers do things differently, so there is no one answer.  Some assemblers are only single pass, for example.

Assemblers that are two-pass generally do the memory allocation and thereby fix locations in the first pass.  These values are then treated like constants on the second pass where the opcodes are generated.

Two passes allows, for example, putting a table at the bottom of the module, having the assembler automatically compute the size of the table, and then use that size value as a constant in code higher up in the module.  I'll use Microchip MPASM for a PIC 16 in a example since this is a two-pass assembler.  (By the way, the ASM30 assembler for the dsPIC series is one-pass, and suffers from limitations as a result).  Consider this code:


         movlw   table_size  ;get number of entries in list

         ...

table
         dw      11          ;list of favorite values
         dw      13
         dw      17
         dw      19
table_size equ   $ - table   ;number of list entries

The $-table computation is performed in the first pass and the value of TABLE_SIZE set accordingly.  This is then used in the second pass when the MOVLW instruction is generated.  This sort of thing is not possible with a one-pass assembler like ASM30.