Q:

O que é assembler?

Sempre leio coisas relacionadas à assembler e fico confuso. No início, eu pensava que isso era uma confusão e que assembler era a mesma coisa que assembly. Mas parece que não é isso.
O que é assembler? E qual a sua relação com assembly?

A:

Assembler
Assembler, como o próprio nome diz é um montador, e não um compilador, ainda que ele funcione de forma muito semelhante. Ele pega um texto que é um código de programação e transforma em código binário (código de máquina). O que difere de um compilador é justamente que as instruções na linguagem possuem uma relação um para um no código binário. A análise e transformação do código em um montador é muito mais simples que um compilador (o lexer é mais ou menos igual, o parser é mais simples, e a análise semântica em geral não existe).
Assembly
A linguagem de programação de baixo nível é o Assembly (linguagem de montagem), que é assemblada por um assembler (o sufixo er em inglês é igual ao nosso dor e indica um agente de uma ação). Usamos em maiúsculo por ser nome próprio.
Existem dialetos diversos para cada arquitetura física (processador x86, ARM, MIPS, etc.), ou virtual (JVM, CIL, e muitas linguagens dinâmicas possuem seu próprio Assembly). A semântica depende da arquitetura, mas a sintaxe não, e o montador específico pode adotar a que preferir.
Há quem confunda código de máquina e Assembly. O primeiro é binário, o Assembly está em um nível que humanos (normais, embora alguns dirão que esses não são tão normais assim :P) entendem. São mnemônicos que definem as instruções que o processador deve executar.
Assembly é toda imperativa e cada mnemônico é uma instrução muito simples manipulando um valor em registradores, movendo os dados entre os registradores e memória e controlando o fluxo de execução da forma mais simples possível, sem abstrações, sem syntax sugar, sem padrões de projeto prontos.
Quando a pessoa diz que vai programar em assembler ela comete o mesmo erro que a pessoa que diz que vai programar em Visual Studio.
assembly
Mas assembly em minúsculo é aquele arquivo com código binário do CLR (vulgo .NET).
Exemplos
Exemplo de código Assembly x86-64 sintaxe Intel:
; Assembler (x86) version of 99 Bottles of beer
; 
; This version is for NASM compiler but doesn't use any 
; macros, just all basic instructions for x86 assembler.
; Also only putchar() function is used to print character
; onto screen, and the whole rest is in code.
;
; nasm -fwin32 99.asm
; gcc -o 99.exe 99.obj

        global  _main
        extern  _putchar
        
        segment .data

_line_1_1        db ' bottles of beer on the wall, ', 0
_line_1_2        db ' bottles of beer.', 13, 10, 0
_line_2_1        db 'Take one down and pass it around, ', 0
_line_2_2        db ' bottles of beer on the wall.', 13, 10, 13, 10, 0
_line_2_2_one    db ' bottle of beer on the wall.', 13, 10, 13, 10, 0
_ending_lines    db '1 bottle of beer on the wall, 1 bottle of beer.', 13, 10
                 db 'Take one down and pass it around, no more bottles of beer on the wall.', 13,
10, 13, 10
                 db 'No more bottles of beer on the wall, no more bottles of beer. ', 13, 10
                 db 'Go to the store and buy some more, 99 bottles of beer on the wall.', 13, 10, 0
         
        segment .text

; this function converts integer in range 0-99 to string
_integer_to_string:
        mov     eax, dword [esp + 08h]    ; get the vavlue
        mov     ecx, 10                   ; 
        sub     edx, edx                  
        div     ecx                       ; divide it by 10
        mov     ecx, dword [esp + 04h]    ; get the output offset
        test    eax, eax                  ; is greater than 9
        jz      .skip_first_digit         ; skip saving 0 char if no
        add     al, 030h                  ; convert number to ascii char
        mov     byte [ecx], al            ; save
        inc     ecx                       ; increase pointer
        jmp     .dont_test_second_digit   ; 
     .skip_first_digit:                   ; only if less then 10
        test    edx, edx
        jz      .skip_second_digit
     .dont_test_second_digit:             ; if it was greater than 10
        add     dl, 030h                  ; than second digit must by 
        mov     byte [ecx], dl            ; written at no condition
        inc     ecx                     
     .skip_second_digit:                  ; only skip if value was 0
        mov     byte [ecx], ah            ; save the null ending char
        retn    4                         ; ret and restore stack
; function prints null-terminated line to stdout
_show_line:
        push    edi                       ; function save registers
        push    esi
        mov     edi, dword [esp + 0Ch]    ; get the pointer to string
        sub     eax, eax                  ; look for zeros
        sub     ecx, ecx                        
        dec     ecx                       ; set ecx to -1
        repnz   scasb                     ; search for 0 in string
        neg     ecx
        sub     ecx, 2                    ; get the string length w/o zero
        mov     esi, dword [esp + 0Ch]    ; get pointer once again
     .putchar_loop:
        push    ecx                       ; keep the counter
        lodsb                             ; get the char
        push    eax                       
        call    _putchar                  ; print char to stdout
        add     esp, 4                    ; correct stack 
        pop     ecx                       ; get back the counter
        dec     ecx                     
        jnz     .putchar_loop             ; if not last char then get next
        pop     esi                       ; restore registers
        pop     edi
        retn    4
; prints string for only one number
_bottles:
        push    ebp                       ; keep the offset to call params
        mov     ebp, esp
        sub     esp, 4                    ; reserve one local variable
        mov     eax, dword [ebp + 08h]    ; get number of bottles
        dec     eax                       ; is it 1?
        jnz     .more_than_one            ; nope, it's not
        push    _ending_lines             ; print the last lines
        call    _show_line
        jmp     .end                      ; exit function
     .more_than_one:
        inc     eax                       ; get the original value
        push    eax                       ; convert it to string
        lea     eax, [ebp - 04h]
        push    eax                       ; string will be stored here
        call    _integer_to_string
        lea     eax, [ebp - 04h]
        push    eax
        call    _show_line                ; 'xx'
        push    _line_1_1
        call    _show_line                ; ' bottles of beer on the wall, '
        lea     eax, [ebp - 04h]
        push    eax
        call    _show_line                ; 'xx'
        push    _line_1_2
        call    _show_line                ; ' bottles of beer.'
        mov     eax, dword [ebp + 08h]
        dec     eax                       ; in second line the value is one less
        push    eax
        lea     eax, [ebp - 04h]
        push    eax
        call    _integer_to_string        ; convert it to string
        push    _line_2_1
        call    _show_line                ; 'Take one down and pass it around, '
        lea     eax, [ebp - 04h]
        push    eax
        call    _show_line                ; 'xx'
        cmp     dword [ebp + 08h], 2
        jnz     .second_line_for_more_than_one
        push    _line_2_2_one             ; ' bottle of beer on the wall.'
        jmp     .show_line
     .second_line_for_more_than_one:   
        push    _line_2_2                 ; ' bottles of beer on the wall.'
     .show_line:
        call    _show_line
     .end:
        leave
        retn    4
; main function, the command line arguments are not important
_main:        
        pushad
        mov     ecx, 99                   ; printf from 99
     .main_loop:
        push    ecx
        push    ecx
        call    _bottles                  ; print lines for this value
        pop     ecx
        loop    .main_loop                ; if still greater than zero
        popad
        sub     eax, eax                  ; That's all folks!
        retn

No ARM:
;99 Bottles of Beer generator
;For ARM processors running RISCOS
;Using built in BASIC assembler
;

MOV R7, #99              ;bottle count kept in R7
MOV R12, R14             ;store caller return address

.beginverse              ;(_prints verses then returns to caller_)
BL  bottlesofbeer
ADR R0, onthewall
SWI "OS_Write0"          ;prints string at address in R0
BL  bottlesofbeer
SWI "OS_NewLine"
ADR R0, take
SWI "OS_Write0"
SUBS R7,R7,#1            
BLNE bottlesofbeer       ;beer left
BLEQ nobeer              ;no beer left
ADR R0, onthewall
SWI "OS_Write0"
SWI "OS_NewLine"
SWI "OS_NewLine"
BNE beginverse           ;go again if there's beer left
BL buymorebeer           ;print last verse
MOV PC, R12              ;exit to caller

.bottlesofbeer           ;(_prints "x bottle(s) of beer"_)
MOV R0, R7               ;arg1- number of bottles
ADR R1, bottlenum        ;arg2- buffer address
MOV R2, #3               ;arg3- buffer length
SWI "OS_ConvertInteger3" ;convert number of beers to string
SWI "OS_Write0"          ;and print it
CMP R7, #1             
ADR R0, bottles          ;
ADREQ R0, bottle         ;bottles is replaced with bottle if 1 bottle left
SWI "OS_Write0"
ADR R0, ofbeer
SWI "OS_Write0"
CMP R1, #0               ;unset zero flag so "nobeer" doesnt execute after return
MOV PC, R14              ;return

.buymorebeer             ;(_prints final verse_)
MOV R11, R14             ;save return address
BL nobeer
ADR R0, onthewall
SWI "OS_Write0"
ADR R0, comma
SWI "OS_Write0"
BL nobeer
SWI "OS_NewLine"
ADR R0, gotostore
SWI "OS_Write0"
MOV PC, R11              ;return to saved address

.nobeer                  ;(_prints "no more bottles of beer"_)
ADR R0, nomore
SWI "OS_Write0"
ADR R0, bottles
SWI "OS_Write0"
ADR R0, ofbeer
SWI "OS_Write0"
MOV PC, R14

;string components

.ofbeer
EQUS "of beer"           ;string contents
EQUB 0                   ;zero terminator

.onthewall
EQUS " on the wall "
EQUB 0

.bottle
EQUS " bottle "
EQUB 0

.bottles
EQUS " bottles "
EQUB 0

.take
EQUS "Take one down and pass it around, "
EQUB 0

.nomore
EQUS "no more"
EQUB 0

.bottlenum
EQUS "  "
EQUB 0

.comma
EQUS ","
EQUB 0

.gotostore
EQUS "Go to the store and buy some more...99 bottles of beer."
EQUB 0

Fonte.
Percebeu porque falam que deve comentar seu código? Era aí que deveria.
Perguntas relacionadas:

Como é feito um compilador?
O que é linguagem de programação, IDE e compilador?
A primeira linguagem de programação

A:

O que é assembler? E qual a sua relação com assembly?

Assembler é um compilador. Ele converte código escrito na linguagem Assembly para código nativo.

A:

Assembly é uma linguagem de programação. Mas não é uma linguagem típica. Ela é caracterizada por ser uma linguagem de programação de baixo nível composta por instruções de formato rígido e simples que não permitem subestruturas e por alguns labels (rótulos que são alvos para instruções de desvio). Ela normalmente (quase sempre, mas há alguns casos que não) é mapeada de um para um em instruções a serem executadas pelo processador (uma instrução do processador = uma instrução em assembly).
Cada instrução é definida por um mnemônico. Por exemplo, em uma instrução MOV eax, 1 ou JN algum_label, o MOV e o JN são os mnemônicos, que são os que definem também o nome da instrução em questão.
Já o assembler é o programa que converte o código em assembly para as instruções propriamente ditas (codificadas como uma sequência de bytes). Ou seja, o assembler é o compilador. Apesar disso, a confusão entre os termos assembly e assembler é comum, e muita gente fala de "programar em assembler" quando na verdade deveria ser "programar em assembly".
Uma vez que há diversos tipos de processadores, cada qual com o seu conjunto de instruções, isso significa que para cada processador temos ao menos um dialeto de assembly. Diferentes desenvolvedores de assemblers podem usar notações diferentes ou mnemônicos diferentes para as instruções, e portanto, mesmo numa mesma arquitetura, podem haver vários distintos dialetos de assembly.

